黑马头条(APP端)

黑马头条(APP端)

一、项目简介

  • 这个项目类似于今日头条,是一个新闻资讯类项目,涉及技术广涵盖许多微服务的解决方案。
  • 项目包含三个部分
    1. 用户移动app端
    2. 用户自媒体端
    3. 管理平台端
  • 涉及技术栈:
    • SpringBoot+SpringCloud+nacos+swagger+freemarker+OSS+ElasticSearch+Redis+Kafka+KafkaStream+
    • xxl-job+Hbase+Jenkins+Git+Docker+Mysql+MongoDB

二、环境搭建

  1. 使用docker部署nacos

    docker拉取镜像

    docker pull nacos/nacos-server:1.2.0
    

    创建nacos的docker容器(开机启动)

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

    访问nacos

    http://192.168.142.100:8848/nacos
    
  2. 导入基础项目,以及导入数据库和相关表

    配置文件编码、maven仓库等信息。

    注意使用资料的maven仓库,不然有问题。

三、APP端用户登录功能

  1. 在heima-headnews-service下添加模块heima-headnews-user,并创建config、mapper、service、controller.v1包。

  2. 模块添加启动类

    @SpringBootApplication
    @EnableDiscoveryClient
    @MapperScan("com.heima.user.mapper")
    public class UserApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(UserApplication.class,args);
        }
    }
    
  3. 添加配置文件

    logback日志配置

    
    
    <configuration>
        
        <property name="LOG_HOME" value="C:\Users\ASUS\Desktop\logs"/>
    
        
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%npattern>
                <charset>utf8charset>
            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}.logfileNamePattern>
            rollingPolicy>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%npattern>
            encoder>
        appender>
    
        
        <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
            
            <discardingThreshold>0discardingThreshold>
            
            <queueSize>512queueSize>
            
            <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="FILE"/>
            <appender-ref ref="CONSOLE"/>
        root>
    configuration>
    

    user模块配置

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

    nacos配置中心user服务配置

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/leadnews_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
        username: root
        password: 1234
    # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
    mybatis-plus:
      mapper-locations: classpath*:mapper/*.xml
      # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
      type-aliases-package: com.heima.model.user.pojos
    
  4. 业务实现--------------------------(小技巧:注释都写正常的流程)

    //app端用户登录功能
    @Override
    public ResponseResult login(LoginDto dto) {
        HashMap<String, Object> map = new HashMap<>();//返回结果的map
        // 1. 用户登录
        if (!StringUtils.isBlank(dto.getPhone()) || !StringUtils.isBlank(dto.getPassword())) {
            // 1.1获取登录用户
            ApUser user = getOne(Wrappers.<ApUser>lambdaQuery().eq(ApUser::getPhone, dto.getPhone()));
            if (user == null){
                return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"用户不存在!");
            }
            // 1.2验证密码
            String salt = user.getSalt();
            String password = dto.getPassword();
            String pswd = DigestUtils.md5DigestAsHex((password + salt).getBytes());//加密用户输入的密码
            if (!pswd.equals(user.getPassword())){
                return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
            }

            // 1.3生成token,返回数据
            String token = AppJwtUtil.getToken(user.getId().longValue());
            map.put("token",token);
            user.setSalt("");
            user.setPassword("");
            map.put("user",user);
            return ResponseResult.okResult(map);
        }
        // 2. 用户不登陆
        map.put("token",AppJwtUtil.getToken(0L));//游客token的id都是0
        return ResponseResult.okResult(map);
    }

四、集成Swagger、knif4j

集成swagger

1.导入依赖

在common和model模块都添加swagger依赖

        
        <dependency>
            <groupId>io.springfoxgroupId>
            <artifactId>springfox-swagger2artifactId>
        dependency>
        <dependency>
            <groupId>io.springfoxgroupId>
            <artifactId>springfox-swagger-uiartifactId>
        dependency>
2. 添加配置类
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {

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

   private ApiInfo buildApiInfo() {
      Contact contact = new Contact("黑马程序员","","");
      return new ApiInfoBuilder()
              .title("黑马头条-平台管理API文档")
              .description("黑马头条后台api")
              .contact(contact)
              .version("1.0.0").build();
   }
}
3.将配置文件交给spring管理
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.heima.common.exception.ExceptionCatch,\
  com.heima.common.swagger.SwaggerConfiguration
4.Swagger常用注解

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

@Api:修饰整个类,描述Controller的作用

@ApiOperation:描述一个类的一个方法,或者说一个接口

@ApiParam:单个参数的描述信息

@ApiModel:用对象来接收参数

@ApiModelProperty:用对象接收参数时,描述对象的一个字段

@ApiResponse:HTTP响应其中1个描述

@ApiResponses:HTTP响应整体描述

@ApiIgnore:使用该注解忽略这个API

@ApiError :发生错误返回的信息

@ApiImplicitParam:一个请求参数

@ApiImplicitParams:多个请求参数的描述信息

controller层添加注解

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

    @Resource
    private ApUserService apUserService;

    //app端用户登录功能
    @PostMapping("/login_auth")
    @ApiOperation("用户登录")
    public ResponseResult login(@RequestBody LoginDto dto){
        return apUserService.login(dto);
    }
}

实体类添加注解

@Data
public class LoginDto {
    //手机号
    @ApiModelProperty(value = "手机号")
    private String phone;
    //密码
    @ApiModelProperty(value = "密码")
    private String password;
}
5.启动

启动user微服务,访问地址:http://localhost:51801/swagger-ui.html

集成knif4j

1.导入依赖

common中导入即可

<dependency>
     <groupId>com.github.xiaoymingroupId>
     <artifactId>knife4j-spring-boot-starterartifactId>
dependency>
2.添加配置类
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();
    }
}
3.将配置文件交给spring管理
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.heima.common.exception.ExceptionCatch,\
  com.heima.common.swagger.SwaggerConfiguration,\
  com.heima.common.swagger.Swagger2Configuration
4.启动

在浏览器输入地址:http://localhost:51801/doc.html

五、整合网关

1.导入依赖

geteway模块中

<dependencies>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-gatewayartifactId>
    dependency>
    <dependency>
        <groupId>com.alibaba.cloudgroupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    dependency>
     <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
        dependency>
    <dependency>
        <groupId>io.jsonwebtokengroupId>
        <artifactId>jjwtartifactId>
    dependency>
dependencies>

2.添加配置

app-gateway配置文件bootstrap.yml

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

注册中心网关配置

首先,在spring.cloud.gateway下的gateway属性中,我们定义了全局的CORS跨域请求配置。

  1. globalcors属性指定了将CORS配置添加到简单URL处理程序映射中

  2. corsConfigurations属性定义了实际的CORS规则。在这里,使用通配符[/**]匹配所有的URL路径,然后设置了允许的请求头和来源,以及允许的请求方法。

然后,在routes属性中,我们定义了路由规则。凡是以/user开头的请求都转发到leadnews-user服务处理,lb://是负载均衡。然后StripPrefix过滤规则会去除前缀/user。

这段配置的作用是在Spring Cloud Gateway中实现了CORS跨域请求的配置路由规则的定义,使请求可以正确地被转发到后端服务中。

spring:
  cloud:
    gateway:
      globalcors:
        add-to-simple-url-handler-mapping: true
        corsConfigurations:
          '[/**]':
            allowedHeaders: "*"
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - DELETE
              - PUT
              - OPTION
      routes:
        # 平台管理
        - id: user
          uri: lb://leadnews-user
          predicates:
            - Path=/user/**
          filters:
            - StripPrefix= 1

3.添加启动

@SpringBootApplication
@EnableDiscoveryClient  //开启注册中心
public class AppGatewayApplication {

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

4.检测

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

如果可以登录成功,说明网关搭建成功

5.全局过滤器校验token

记得添加一个jwt的工具类到app的网关里

@Component
@Slf4j
public class AuthorizeFilter implements Ordered, GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 1.获取request和response
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        // 2.获取访问路径,判断是否为登录路径
        if (request.getURI().getPath().contains("/login")){
            return chain.filter(exchange);//放行
        }
        // 3.获取token
        String token = request.getHeaders().getFirst("token");
        // 4.判断token是否存在以及过期
        if (StringUtils.isBlank(token)){
            //token不存在设置状态码返回
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        try {
            Claims claimsBody = AppJwtUtil.getClaimsBody(token);
            int res = AppJwtUtil.verifyToken(claimsBody);
            //token过期了返回
            if (res == 1 || res == 2){
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
        } catch (Exception e) {
            e.printStackTrace();
            //解析失败也返回
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

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

    //过滤器的优先级,数值越小,优先级越高
    @Override
    public int getOrder() {
        return 0;
    }
}

六、部署静态资源

在nginx安装的conf目录下新建一个文件夹leadnews.conf,在当前文件夹中新建heima-leadnews-app.conf文件

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

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

server {
	listen 8801;
	location / {
		root html/app-web/;
		index index.html;
	}
	
	location ~/app/(.*) {
		proxy_pass http://heima-app-gateway/$1;
		proxy_set_header HOST $host;  # 不改变源请求头的值
		proxy_pass_request_body on;  #开启获取请求体
		proxy_pass_request_headers on;  #开启获取请求头
		proxy_set_header X-Real-IP $remote_addr;   # 记录真实发出请求的客户端IP
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  #记录代理信息
	}
}

nginx.conf 把里面注释的内容和静态资源配置相关删除,引入heima-leadnews-app.conf文件加载

#user  nobody;
worker_processes  1;

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

然后直接nginx -s reload

再重新启动nginx

七、文章列表查看

1.导入数据库表以及文章微服务模块

文章相关表都进行了垂直分表

垂直分表:将一个表的字段分散到多个表中,每个表存储其中一部分字段。

优势:

  1. 减少IO争抢,减少锁表的几率,查看文章概述与文章详情互不影响

  2. 充分发挥高频数据的操作效率,对文章概述数据操作的高效率不会被操作文章详情数据的低效率所拖累。

拆分规则:

  1. 把不常用的字段单独放在一张表

  2. 把text,blob等大字段拆分出来单独放在一张表

  3. 经常组合查询的字段单独放在一张表中

2.添加nacos配置

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/leadnews_article?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 1234
# 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
mybatis-plus:
  mapper-locations: classpath*:mapper/*.xml
  # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
  type-aliases-package: com.heima.model.article.pojos

3.导入文章dto类和常量类

文章dto类

package com.heima.model.article.dtos;

import lombok.Data;

import java.util.Date;

@Data
public class ArticleHomeDto {

    // 最大时间
    Date maxBehotTime;
    // 最小时间
    Date minBehotTime;
    // 分页size
    Integer size;
    // 频道ID
    String tag;
}

常量类(common中添加)

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

}

4.编写业务逻辑

mapper接口

@Repository
public interface ApArticleMapper extends BaseMapper<ApArticle> {

    //根据type类型加载数据,1表示加载更多,2表示加载最新
    List<ApArticle> load(@Param("dto") ArticleHomeDto dto,@Param("type") Short type);
}

mapper映射文件

是用于转义的

    
    <resultMap id="resultMap" type="com.heima.model.article.pojos.ApArticle">
        <id column="id" property="id"/>
        <result column="title" property="title"/>
        <result column="author_id" property="authorId"/>
        <result column="author_name" property="authorName"/>
        <result column="channel_id" property="channelId"/>
        <result column="channel_name" property="channelName"/>
        <result column="layout" property="layout"/>
        <result column="flag" property="flag"/>
        <result column="images" property="images"/>
        <result column="labels" property="labels"/>
        <result column="likes" property="likes"/>
        <result column="collection" property="collection"/>
        <result column="comment" property="comment"/>
        <result column="views" property="views"/>
        <result column="province_id" property="provinceId"/>
        <result column="city_id" property="cityId"/>
        <result column="county_id" property="countyId"/>
        <result column="created_time" property="createdTime"/>
        <result column="publish_time" property="publishTime"/>
        <result column="sync_status" property="syncStatus"/>
        <result column="static_url" property="staticUrl"/>
    resultMap>
    
    <select id="loadArticleList" resultMap="resultMap">
        SELECT
        aa.*
        FROM
        `ap_article` aa
        LEFT JOIN ap_article_config aac ON aa.id = aac.article_id
        <where>
            and aac.is_delete != 1
            and aac.is_down != 1
            
            <if test="type != null and type == 1">
                and aa.publish_time  #{dto.minBehotTime}
            if>
                                               
            <if test="type != null and type == 2">
                and aa.publish_time ]]> #{dto.maxBehotTime}
            if>
            
            <if test="dto.tag != '__all__'">
                and aa.channel_id = #{dto.tag}
            if>
        where>
        order by aa.publish_time desc
        limit #{dto.size}
    select>

业务代码

    @Resource
    private ApArticleMapper apArticleMapper;

    // 单页最大加载的数字
    private final static short MAX_PAGE_SIZE = 50;

    /**
     * 根据类型加载文章
     *
     * @param loadtype
     * @param dto
     * @return
     */
    @Override
    public ResponseResult load(Short loadtype, ArticleHomeDto dto) {
        //1.校验参数
        Integer size = dto.getSize();
        if(size == null || size == 0){
            size = 10;
        }
        size = Math.min(size,MAX_PAGE_SIZE);
        dto.setSize(size);

        //类型参数检验
        if(!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_MORE)&&!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_NEW)){
            loadtype = ArticleConstants.LOADTYPE_LOAD_MORE;
        }
        //文章频道校验
        if(StringUtils.isEmpty(dto.getTag())){
            dto.setTag(ArticleConstants.DEFAULT_TAG);
        }

        //时间校验
        if(dto.getMaxBehotTime() == null) dto.setMaxBehotTime(new Date());
        if(dto.getMinBehotTime() == null) dto.setMinBehotTime(new Date());
        //2.查询数据
        List<ApArticle> apArticles = apArticleMapper.loadArticleList(dto, loadtype);

        //3.结果封装
        ResponseResult responseResult = ResponseResult.okResult(apArticles);
        return responseResult;
    }

controller层

@RestController
@RequestMapping("/api/v1/article")
public class ArticleHomeController {
    
    @Resource
    private ApArticleService apArticleService;

    //加载首页
    @PostMapping("/load")
    public ResponseResult load(@RequestBody ArticleHomeDto dto){
        return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_MORE,dto);
    }

    //加载更多
    @PostMapping("/loadmore")
    public ResponseResult loadmore(@RequestBody ArticleHomeDto dto){
        return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_MORE,dto);
    }

    //加载最新
    @PostMapping("/loadnew")
    public ResponseResult loadnew(@RequestBody ArticleHomeDto dto){
        return apArticleService.load(ArticleConstants.LOADTYPE_LOAD_NEW,dto);
    }
}

网关配置文件添加文章服务的路由

        # 文章管理
        - id: article
          uri: lb://leadnews-article
          predicates:
            - Path=/article/**
          filters:
            - StripPrefix= 1

八、文章详情查看

1.在artile微服务中添加MinIO(是我们自定义的starter)和freemarker的支持

添加依赖

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-freemarkerartifactId>
        dependency>

        <dependency>
            <groupId>com.heimagroupId>
            <artifactId>heima-file-starterartifactId>
            <version>1.0-SNAPSHOTversion>
        dependency>

添加配置

  freemarker:
    cache: false  #关闭模板缓存,方便测试
    settings:
      template_update_delay: 0 #检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试
    suffix: .ftl               #指定Freemarker模板文件的后缀名
minio:
  accessKey: miniouser
  secretKey: miniouser
  bucket: leadnews
  endpoint: http://192.168.142.100:9000
  readPath: http://192.168.142.100:900

2.资料中找到模板文件(article.ftl)拷贝到article微服务下

3.资料中找到index.js和index.css两个文件上传到MinIO中

4.新建ApArticleContentMapper

package com.heima.article.mapper;

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

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

6.在artile微服务中新增测试类(后期新增文章的时候创建详情静态页,目前暂时手动生成)

package com.heima.article.test;


import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.heima.article.ArticleApplication;
import com.heima.article.mapper.ApArticleContentMapper;
import com.heima.article.mapper.ApArticleMapper;
import com.heima.file.service.FileStorageService;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ApArticleContent;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.lang3.StringUtils;
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.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

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

    @Autowired
    private Configuration configuration;

    @Autowired
    private FileStorageService fileStorageService;


    @Autowired
    private ApArticleMapper apArticleMapper;

    @Autowired
    private ApArticleContentMapper apArticleContentMapper;

    //根据文章id查询文章内容,利用freemarker模板引擎生成html文件,上传至minio,并将静态文件访问路径设置给文章StaticUrl属性
    @Test
    public void createStaticUrlTest() throws Exception {
        //1.获取文章内容
        ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery().eq(ApArticleContent::getArticleId, 1390536764510310401L));
        if(apArticleContent != null && StringUtils.isNotBlank(apArticleContent.getContent())){
            //2.文章内容通过freemarker生成html文件
            StringWriter out = new StringWriter();
            Template template = configuration.getTemplate("article.ftl");

            Map<String, Object> params = new HashMap<>();
            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);

        }
    }
}

你可能感兴趣的:(项目,spring,cloud,spring,boot)