首页 > 其他分享 >2024/9/4黑马头条跟学笔记(二)

2024/9/4黑马头条跟学笔记(二)

时间:2024-09-05 08:53:46浏览次数:7  
标签:heima org private 2024 import 跟学 article com 头条

app端文章列表

学习内容

需求分析

上方分类频道切换

布局,无图,单图,三张图

image-20240903113610243

文章数据库表

导入文章数据库

结构分析

image-20240903112319319

image-20240903112344716

配置-文章 一对一,拆表,冷热数据分离满足范式

表的拆分-垂直分表

image-20240903113812993

优势

查文章信息不会连带查询文章内容

将longtext字段拆除去,高频查询提高性能

拆分规则

  1. 不常用的放一起
  2. text,blob放一起
  3. 组合查询字段放一起

拷贝实体类

dtos包下
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;
}

pojos包下
package com.heima.model.article.pojos;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * <p>
 * 文章信息表,存储已发布的文章
 * </p>
 *
 * @author itheima
 */

@Data
@TableName("ap_article")
public class ApArticle implements Serializable {

    @TableId(value = "id",type = IdType.ID_WORKER)
    private Long id;


    /**
     * 标题
     */
    private String title;

    /**
     * 作者id
     */
    @TableField("author_id")
    private Long authorId;

    /**
     * 作者名称
     */
    @TableField("author_name")
    private String authorName;

    /**
     * 频道id
     */
    @TableField("channel_id")
    private Integer channelId;

    /**
     * 频道名称
     */
    @TableField("channel_name")
    private String channelName;

    /**
     * 文章布局  0 无图文章   1 单图文章    2 多图文章
     */
    private Short layout;

    /**
     * 文章标记  0 普通文章   1 热点文章   2 置顶文章   3 精品文章   4 大V 文章
     */
    private Byte flag;

    /**
     * 文章封面图片 多张逗号分隔
     */
    private String images;

    /**
     * 标签
     */
    private String labels;

    /**
     * 点赞数量
     */
    private Integer likes;

    /**
     * 收藏数量
     */
    private Integer collection;

    /**
     * 评论数量
     */
    private Integer comment;

    /**
     * 阅读数量
     */
    private Integer views;

    /**
     * 省市
     */
    @TableField("province_id")
    private Integer provinceId;

    /**
     * 市区
     */
    @TableField("city_id")
    private Integer cityId;

    /**
     * 区县
     */
    @TableField("county_id")
    private Integer countyId;

    /**
     * 创建时间
     */
    @TableField("created_time")
    private Date createdTime;

    /**
     * 发布时间
     */
    @TableField("publish_time")
    private Date publishTime;

    /**
     * 同步状态
     */
    @TableField("sync_status")
    private Boolean syncStatus;

    /**
     * 来源
     */
    private Boolean origin;

    /**
     * 静态页面地址
     */
    @TableField("static_url")
    private String staticUrl;
}

package com.heima.model.article.pojos;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

/**
 * <p>
 * APP已发布文章配置表
 * </p>
 *
 * @author itheima
 */

@Data
@TableName("ap_article_config")
public class ApArticleConfig implements Serializable {

    @TableId(value = "id",type = IdType.ID_WORKER)
    private Long id;

    /**
     * 文章id
     */
    @TableField("article_id")
    private Long articleId;

    /**
     * 是否可评论
     * true: 可以评论   1
     * false: 不可评论  0
     */
    @TableField("is_comment")
    private Boolean isComment;

    /**
     * 是否转发
     * true: 可以转发   1
     * false: 不可转发  0
     */
    @TableField("is_forward")
    private Boolean isForward;

    /**
     * 是否下架
     * true: 下架   1
     * false: 没有下架  0
     */
    @TableField("is_down")
    private Boolean isDown;

    /**
     * 是否已删除
     * true: 删除   1
     * false: 没有删除  0
     */
    @TableField("is_delete")
    private Boolean isDelete;
}

package com.heima.model.article.pojos;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

@Data
@TableName("ap_article_content")
public class ApArticleContent implements Serializable {

    @TableId(value = "id",type = IdType.ID_WORKER)
    private Long id;

    /**
     * 文章id
     */
    @TableField("article_id")
    private Long articleId;

    /**
     * 文章内容
     */
    private String content;
}

sql实现思路

默认展示10条文章

切换频道

下拉刷新,新数据必须大于当前10条数据最新的那条,由于是降序排列,时间大于第一条,《什么是java语言》

上拉加载,新数据必须小于当前10条数据最晚那条,也就是最下面的那条信息,因为要加载早期发布的,所以其时间戳要小一点

如果为首页,就展示十条最新数据即可,时间戳小于2063年

查询语句

SELECT * from ap_article aa 
LEFT JOIN ap_article_config aac ON aa.id=aac.article_id
WHERE publish_time >'2020-09-08'
AND channel_id=1
AND aac.is_down!=1
AND aac.is_delete!=1
ORDER BY created_time DESC
LIMIT 10 

最新10,加载老一点的10,左连接条件约束不能下架且不能删除,降序查

接口实现

最大时间,最小时间,分页大小,频道id

image-20240903122047409

步骤

image-20240903122117353

注意,解压缩后的目录不能直接复制粘贴,外面多了一层同名的目录

导入微服务

image-20240903143408154

image-20240903150143001

导入成功后的文件前面有个小蓝点,否则在里面创建不了class

nacos添加配置

image-20240903143706407

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

路由服务增加新的服务

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
        # 文章微服务
        - id: article
          uri: lb://leadnews-article
          predicates:
            - Path=/article/**
          filters:
            - StripPrefix= 1

定义接口

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

编写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> {
    
        /**
     * 加载文章列表 1为加载更多,2为加载最新
     * @param dto
     * @param type
     * @return
     */

    public List<ApArticle> loadArticleList(@Param("dto") ArticleHomeDto dto, @Param("type") Short type);

}

编写xml

(resources/mapper/ApArticleMapper.xml)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.heima.article.mapper.ApArticleMapper">

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

</mapper>

<if test="dto.tag != '__all__'">
    and aa.channel_id = #{dto.tag}
</if>

意思为不为推荐页,则按频道分类

常量

由于1,2难分辨代表啥意思,我们抽成常量类

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

}

业务层

ApArticleService

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;

public interface ApArticleService extends IService<ApArticle> {

    /**
     * 加载文章列表
     * @param dto
     * @param type  1为加载更多,2为加载最新
     * @return
     */
    public ResponseResult load(ArticleHomeDto dto,Short type);
}

ApArticleServiceImpl

@Service
package com.heima.article.service.impl;

import com.alibaba.cloud.commons.lang.StringUtils;
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 org.springframework.beans.factory.annotation.Autowired;

import java.util.Date;
import java.util.List;

public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {

    @Autowired
    ApArticleMapper apArticleMapper;

    /**
     * 加载文章列表
     *
     * @param dto
     * @param type 1为加载更多,2为加载最新
     * @return
     */
    @Override
    public ResponseResult load(ArticleHomeDto dto, Short loadtype) {

        //参数校验

        //判断大小是否正确
        Integer size = dto.getSize();
        if(size==null||size==0){
            size=Math.min(size,50);
        }
        //类型参数检验,既不为1,加载更多也不为2加载最新,那么就默认1加载更多
        if(!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_MORE)&&!loadtype.equals(ArticleConstants.LOADTYPE_LOAD_NEW)){
            loadtype = ArticleConstants.LOADTYPE_LOAD_MORE;
        }
        //文章频道校验,如果不指定频道,那就是首页,直接加载最新10条
        if(StringUtils.isEmpty(dto.getTag())){
            dto.setTag(ArticleConstants.DEFAULT_TAG);
        }
        //时间校验。如果没有最大和最小时间,那么说明时间范围为无限,此时降序展示10条最新数据,与前面的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);
        return ResponseResult.okResult(apArticles);
    }
}

接口完善

package com.heima.article.controller.v1;

import com.heima.article.service.ApArticleService;
import com.heima.common.constants.ArticleConstants;
import com.heima.model.article.dtos.ArticleHomeDto;
import com.heima.model.common.dtos.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/article")
public class ArticleHomeController {

    @Autowired
    private ApArticleService apArticleService;

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

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

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

路由服务增加转发列表

在nacos里

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" #跨域处理 允许所有的域
            allowedMethods: # 支持的方法
              - GET
              - POST
              - PUT
              - DELETE
      routes:
        # 用户微服务
        - id: user
          uri: lb://leadnews-user
          predicates:
            - Path=/user/**
          filters:
            - StripPrefix= 1
        # 文章微服务
        - id: article
          uri: lb://leadnews-article
          predicates:
            - Path=/article/**
          filters:
            - StripPrefix= 1

测试加载更多和最新

app文章详情

实现方案一

image-20240903211243493

根据id查

实现方案2

image-20240903211430150

内容通过模板技术生成html文件

存入分布式系统minIO,同时有一个url存入数据库

用户直接访问url,不需要查库

大公司做longtext和blob时使用

freemarker

介绍

模板引擎

模板+数据生成HTML

模板编写全拼为 freemarker template language FTL

image-20240904140355892

常见java模板引擎

image-20240904140900708

jsp(javaWeb里的servlet,前后端不分离),

freemarker 好,快

thymeleaf 好,但慢

velocity 不好

作为springmvc视图格式,且支持渲染视图

环境搭建

在测试模块下创建子工程,并添加依赖

依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <!-- apache 对 java io 的封装工具库 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>
    </dependencies>

application.yml配置文件

server:
  port: 8881 #服务端口
spring:
  application:
    name: freemarker-demo #指定服务名
  freemarker:
    cache: false  #关闭模板缓存,方便测试
    settings:
      template_update_delay: 0 #检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试
    suffix: .ftl               #指定Freemarker模板文件的后缀名

关缓存,修改数据实时更新

启动类

package com.heima.freemarker;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.swing.*;

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

实体类

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;//钱包
}

在resources下创建templates文件夹,存入模板文件

image-20240904142204443

对象啥数据,模板就啥数据

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Hello World!</title>
</head>
<body>
<b>普通文本 String 展示:</b><br><br>
Hello ${name} <br>
<hr>
<b>对象Student中的数据展示:</b><br/>
姓名:${stu.name}<br/>
年龄:${stu.age}
<hr>
</body>
</html>

接口

package com.heima.freemarker.controller;


import com.heima.freemarker.entity.Student;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.GetMapping;

// 此除不用restController,返回视图而非字符串,使用controller即可
@Controller
public class HelloController {
    @GetMapping("/basic")
    public String hello(Model model) {
        // name
        model.addAttribute("name", "hello freemarker");
        // stu
        Student student = new Student();
        student.setAge(18);
        student.setName("小明");
        model.addAttribute("stu", student);
        return "01-basic";
    }
}

访问测试

Hello World!

image-20240904142836287

如何替换的

加了依赖,相关类在启动时加到了spring容器,默认配置后缀ftlh(不过通常以ftl结尾),模板路径classpath/templates

image-20240904143317385

image-20240904143132288

image-20240904143402345

指令语法

image-20240904143422251

image-20240904145945650

集合指令

增加接口

@GetMapping("/list")
    public String list(Model model){

        //------------------------------------
        Student stu1 = new Student();
        stu1.setName("A");
        stu1.setAge(18);
        stu1.setMoney(666f);
        stu1.setBirthday(new Date());

        //小红对象模型数据
        Student stu2 = new Student();
        stu2.setName("B");
        stu2.setMoney(999f);
        stu2.setAge(19);

        //将两个对象模型数据存放到List集合中
        List<Student> stus = new ArrayList<>();
        stus.add(stu1);
        stus.add(stu2);

        //向model中存放List集合数据
        model.addAttribute("stus",stus);

        //------------------------------------

        //创建Map数据
        HashMap<String,Student> stuMap = new HashMap<>();
        stuMap.put("stu1",stu1);
        stuMap.put("stu2",stu2);
        // 3.1 向model中存放Map数据
        model.addAttribute("stuMap", stuMap);

        return "02-list";
    }

增加模板文件list.ftl

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Hello World!</title>
</head>
<body>

<#-- list 数据的展示 -->
<b>展示list中的stu数据:</b>
<br>
<br>
<table>
    <tr>
        <td>序号</td>
        <td>姓名</td>
        <td>年龄</td>
        <td>钱包</td>
    </tr>
    <#list stus as s>
    <tr>
        <td>${s_index+1}</td>
        <td>${s.name}</td>
        <td>${s.age}</td>
        <td>${s.money}</td>
    </tr>
    </#list>
</table>
<hr>

<#-- Map 数据的展示 -->
<b>map数据的展示:</b>
<br/><br/>
<a href="###">方式一:通过map['keyname'].property</a><br/>
输出stu1的学生信息:<br/>
姓名:<br/>${stuMap.stu1.name}
年龄:<br/>${stuMap.stu1.age}
<br/>
<a href="###">方式二:通过map.keyname.property</a><br/>
输出stu2的学生信息:<br/>
姓名:<br/>${stuMap["stu2"].name}
年龄:<br/>${stuMap["stu2"].age}

<br/>
<a href="###">遍历map中两个学生信息:</a><br/>
<table>
    <tr>
        <td>序号</td>
        <td>姓名</td>
        <td>年龄</td>
        <td>钱包</td>
    </tr>
    <#list stuMap?keys as key>
    <tr>
        <td>${key_index+1}</td>
        <td>${stuMap[key].name}</td>
        <td>${stuMap[key].age}</td>
        <td>${stuMap[key].money}</td>
    </tr>
    </#list>
</table>
<hr>

</body>
</html>

<#list stus as s>遍历

子标签 ${s.name}获取值

获取索引值为s_index也可以+1处理

map值获取用stu.[“学生1”].age

或者用stu.学生1.age

遍历则使用kes as key

然后key为每个对象,用上述方法即可

if指令

 <#if s.name=="A">
            <tr style="color: red">
                <td>${s_index+1}</td>
                <td>${s.name}</td>
                <td>${s.age}</td>
                <td>${s.money}</td>
            </tr>
        <#else>
            <tr>
                <td>${s_index+1}</td>
                <td>${s.name}</td>
                <td>${s.age}</td>
                <td>${s.money}</td>
            </tr>
        </#if>

=与==号用法一致

image-20240904152811499

运算符

image-20240904153135102

image-20240904153152589

gt代替> 可能会被当作结束字符<#if (x>y)>

image-20240904153445644

空值处理

防止报错,代码健壮性

image-20240904153517170

外层嵌套

 <#if stus??>
  </#if>

变量空值处理

${name!""}

加了个!“”

也可以在""里加入你想要填入的默认值

${name!"============"}

image-20240904154016498

image-20240904154058301

内建函数

用法

集合大小

${变量+?+函数名称}

image-20240904154323777

日期格式化+去除每三位生成的一逗号

${today?datetime}
<br>
${today?string("yyyy年MM月")}
<br>
${score?c}
        model.addAttribute("today", new Date());
        model.addAttribute("score", 8823333333333333f);

image-20240904154911866

json转为字符串

image-20240904155051538

image-20240904155116297

静态文件生成

配置文件增加引用路径

template-loader-path: classpath:/templates   #模板存放位置

新建测试类,引入freemarker上下文

image-20240904160659991
import com.heima.freemarker.FreeMarkerDemoApplication;
import com.heima.freemarker.entity.Student;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.FileWriter;
import java.io.IOException;
import java.util.*;

@SpringBootTest(classes = FreeMarkerDemoApplication.class)
@RunWith(SpringRunner.class)
public class FreemarkerTest {
    @Autowired
    private Configuration configuration;

    @Test
    public void test() throws IOException, TemplateException {
        // 1.获取模板对象通过文件名搭配配置文件寻找
        Template template = configuration.getTemplate("02-list.ftl");
        // 2.使用该对象合成模型
        // 参数1为模板文件。2为输出流,指定生成文件的位置
        template.process(getData(), new FileWriter("d:/02-list.html"));
    }


    private Map getData() {
        //------------------------------------
        Map<String, Object> map = new HashMap<>();
        Student stu1 = new Student();
        stu1.setName("A");
        stu1.setAge(18);
        stu1.setMoney(666f);
        stu1.setBirthday(new Date());

        // 小红对象模型数据
        Student stu2 = new Student();
        stu2.setName("B");
        stu2.setMoney(999f);
        stu2.setAge(19);

        // 将两个对象模型数据存放到List集合中
        List<Student> stus = new ArrayList<>();
        stus.add(stu1);
        stus.add(stu2);

        // 向model中存放List集合数据
        map.put("stus", stus);


        //------------------------------------

        // 创建Map数据
        HashMap<String, Student> stuMap = new HashMap<>();
        stuMap.put("stu1", stu1);
        stuMap.put("stu2", stu2);
        // 3.1 向model中存放Map数据
        map.put("stuMap", stuMap);
        map.put("today", new Date());
        map.put("score", 8823333333333333f);
        return map;
    }
}

运行生成模板文件

image-20240904160633313

MinIO

对象存储方式对比

image-20240904160844168

服务器,爆内存要扩麻烦

分布式,构建复杂

第三方,贵,裤兜子里没钢镚买不起

分布式文件系统

minIO nb!

概述

image-20240904161122373

开源,保存大空间

单行命令运行

支持云存储迁移接口,可控大小

环境搭建

image-20240904161241028

拉取镜像

docker pull minio/minio

创建容器

docker run -p 9000:9000 --name minio -d --restart=always -e "MINIO_ROOT_USER=minio" -e "MINIO_ROOT_PASSWORD=minio123" -v /home/data:/data -v /home/config:/root/.minio minio/minio server /data 

开机启动

用户名minio 密码minio123

访问地址 http://192.168.233.136:9000

image-20240904165141276

如果是自己下的镜像那么就会出现上述错误

黑马给我们的古老的minio环境变量产生了错误

修改为MINIO_ROOT_USER MINIO_ROOT_PASSWORD且以下面这条代码启动

  • 映射了内部端口的9001和9000 9000为api,9001为控制台

  • 控制台端口–console-address “:9001” 必须加不加访问不了

    在 MinIO 中,--console-address ":9001" 参数用于指定控制台的访问地址和端口。如果不指定这个参数,控制台将不会自动启动,或可能使用默认设置,导致无法访问。

    主要原因:

    1. 控制台未启用:
    • 默认情况下,MinIO 可能不会启动控制台,除非明确指定其地址和端口。
docker run -p 9000:9000 \-p 9001:9001 --name minio -d --restart=always -e "MINIO_ROOT_USER=minio" -e "MINIO_ROOT_PASSWORD=minio123" -v /home/data:/data -v /home/config:/root/.minio minio/minio server /data 
--console-address ":9001"

基本概念

image-20240904171200479

bucket 图片根目录文件夹

object html文件等等…

keys 文件名

点+建桶 由于黑马的镜像太大 ,自带虚拟机和镜像后续操作版本不和黑马的minio操作一致,不过原理差不多

image-20240904171312732

快速入门

image-20240904171626385

上传list.html到minio 并且访问

创建minio-demo模块

依赖引入

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

引导类

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

上传业务类

package com.heima.minio;

import io.minio.MinioClient;
import io.minio.PutObjectArgs;

import javax.imageio.stream.FileImageInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class MinIOTest {
    public static void main(String[] args) {
        //获取文件流,后续传输到服务器
        try {
            FileInputStream inputStream = new FileInputStream("D:\\02-list.html");
            // 1 创建minio客户端输入密码账号
            MinioClient minioClient = MinioClient.builder().credentials("minio", "minio123").endpoint("http://192.168.233.139:9000").build();
            // 2 上传文件
            // 构建上传配置参数
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .object("list.html")//文件名
                    .contentType("text/html")//文件类型
                    .stream(inputStream, inputStream.available(), -1)//文件流
                    .bucket("leadnews")//桶名
                    .build();
            // 放入客户端
            minioClient.putObject(putObjectArgs);
            // 成功则打印访问地址
            System.out.println("http://192.168.233.139:9000/leadnews/list.html");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }
}

访问连接报错,提示拒绝访问access denied

image-20240904182448412

最新版得设置access权限

image-20240904182424167

访问成功

image-20240904182532896

封装Minio为starter

image-20240904182935567

不需要再每一个微服务里集成,做成一个公共的微服务

步骤

image-20240904183036800

导入公共微服务,文件管理starter

该微服务导入文件管理依赖,添加配置文件,注入service后续调用

父工程添加pom将其纳入子模块

heima-leadnews-basic

image-20240904183520817

然后刷新maven 成功变蓝

image-20240904183558712

项目集成

minio里依赖引入file服务

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

minio-test配置文件

application.yml

minio:
  accessKey: minio
  secretKey: minio123
  bucket: leadnews
  endpoint: http://192.168.233.136:9000
  readPath: http://192.168.233.136:9000

一一对应(prefix标识读取在配置文件里minio的值)

image-20240904184625223

注入FIlestorageService调用测试

package com.heima.minio;

import com.heima.file.service.FileStorageService;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
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.FileInputStream;
import java.io.FileNotFoundException;

// @SpringBootTest(classes = MinIOApplication.class):
// 指定了Spring Boot的启动类,用于加载Spring上下文。

// @RunWith(SpringRunner.class):指定了JUnit的运行器,用于在Spring环境中运行测试
@SpringBootTest(classes = MinIOApplication.class)
@RunWith(SpringRunner.class)
public class MinIOTest {

    @Autowired
    private FileStorageService fileStorageService;

    @Test
    public void test() throws FileNotFoundException {
        FileInputStream inputStream = new FileInputStream("D:\\02-list.html");
        String path = fileStorageService.uploadHtmlFile("", "list.html", inputStream);
        System.out.println(path);
    }

    // public static void main(String[] args) {
    //     //获取文件流,后续传输到服务器
    //     try {
    //         FileInputStream inputStream = new FileInputStream("D:\\02-list.html");
    //         // 1 创建minio客户端输入密码账号
    //         MinioClient minioClient = MinioClient.builder().credentials("minio", "minio123").endpoint("http://192.168.233.136:9000").build();
    //         // 2 上传文件
    //         // 构建上传配置参数
    //         PutObjectArgs putObjectArgs = PutObjectArgs.builder()
    //                 .object("list.html")//文件名
    //                 .contentType("text/html")//文件类型
    //                 .stream(inputStream, inputStream.available(), -1)//文件流
    //                 .bucket("leadnews")//桶名
    //                 .build();
    //         // 放入客户端
    //         minioClient.putObject(putObjectArgs);
    //         // 成功则打印访问地址
    //         System.out.println("http://192.168.233.136:9000/leadnews/list.html");
    //     } catch (Exception e) {
    //         throw new RuntimeException(e);
    //     }
    //
    // }
}

这种方法上传的会产生时间目录

image-20240904201712321

image-20240904201817254

回顾一下

ftl技术 存入minio文件系统,

并存url路径

用户访问该html文件

静态页面生成上传

image-20240904203339479

mapper&&mapperxml

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

article服务添加freemarker依赖和file-starter

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

将minio-demo的配置文件迁移到article服务

minio:
  accessKey: minio
  secretKey: minio123
  bucket: leadnews
  endpoint: http://192.168.233.136:9000
  readPath: http://192.168.233.136:9000

image-20240904204029256

创建模板文件

article.ftl

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport"
          content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover">
    <title>黑马头条</title>
    <!-- 引入样式文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/lib/index.css">
    <!-- 页面样式 -->
    <link rel="stylesheet" href="../../../plugins/css/index.css">
</head>

<body>
<div id="app">
    <div class="article">
        <van-row>
            <van-col span="24" class="article-title" v-html="title"></van-col>
        </van-row>

        <van-row type="flex" align="center" class="article-header">
            <van-col span="3">
                <van-image round class="article-avatar" src="https://p3.pstatp.com/thumb/1480/7186611868"></van-image>
            </van-col>
            <van-col span="16">
                <div v-html="authorName"></div>
                <div>{{ publishTime | timestampToDateTime }}</div>
            </van-col>
            <van-col span="5">
                <van-button round :icon="relation.isfollow ? '' : 'plus'" type="info" class="article-focus"
                            :text="relation.isfollow ? '取消关注' : '关注'" :loading="followLoading" @click="handleClickArticleFollow">
                </van-button>
            </van-col>
        </van-row>

        <van-row class="article-content">
            <#if content??>
                <#list content as item>
                    <#if item.type='text'>
                        <van-col span="24" class="article-text">${item.value}</van-col>
                    <#else>
                        <van-col span="24" class="article-image">
                            <van-image width="100%" src="${item.value}"></van-image>
                        </van-col>
                    </#if>
                </#list>
            </#if>
        </van-row>

        <van-row type="flex" justify="center" class="article-action">
            <van-col>
                <van-button round :icon="relation.islike ? 'good-job' : 'good-job-o'" class="article-like"
                            :loading="likeLoading" :text="relation.islike ? '取消赞' : '点赞'" @click="handleClickArticleLike"></van-button>
                <van-button round :icon="relation.isunlike ? 'delete' : 'delete-o'" class="article-unlike"
                            :loading="unlikeLoading" @click="handleClickArticleUnlike">不喜欢</van-button>
            </van-col>
        </van-row>

        <!-- 文章评论列表 -->
        <van-list v-model="commentsLoading" :finished="commentsFinished" finished-text="没有更多了"
                  @load="onLoadArticleComments">
            <van-row id="#comment-view" type="flex" class="article-comment" v-for="(item, index) in comments" :key="index">
                <van-col span="3">
                    <van-image round src="https://p3.pstatp.com/thumb/1480/7186611868" class="article-avatar"></van-image>
                </van-col>
                <van-col span="21">
                    <van-row type="flex" align="center" justify="space-between">
                        <van-col class="comment-author" v-html="item.authorName"></van-col>
                        <van-col>
                            <van-button round :icon="item.operation === 0 ? 'good-job' : 'good-job-o'" size="normal"
                                        @click="handleClickCommentLike(item)">{{ item.likes || '' }}
                            </van-button>
                        </van-col>
                    </van-row>

                    <van-row>
                        <van-col class="comment-content" v-html="item.content"></van-col>
                    </van-row>
                    <van-row type="flex" align="center">
                        <van-col span="10" class="comment-time">
                            {{ item.createdTime | timestampToDateTime }}
                        </van-col>
                        <van-col span="3">
                            <van-button round size="normal" v-html="item.reply" @click="showCommentRepliesPopup(item.id)">回复 {{
                                item.reply || '' }}
                            </van-button>
                        </van-col>
                    </van-row>
                </van-col>
            </van-row>
        </van-list>
    </div>
    <!-- 文章底部栏 -->
    <van-row type="flex" justify="space-around" align="center" class="article-bottom-bar">
        <van-col span="13">
            <van-field v-model="commentValue" placeholder="写评论">
                <template #button>
                    <van-button icon="back-top" @click="handleSaveComment"></van-button>
                </template>
            </van-field>
        </van-col>
        <van-col span="3">
            <van-button icon="comment-o" @click="handleScrollIntoCommentView"></van-button>
        </van-col>
        <van-col span="3">
            <van-button :icon="relation.iscollection ? 'star' : 'star-o'" :loading="collectionLoading"
                        @click="handleClickArticleCollection"></van-button>
        </van-col>
        <van-col span="3">
            <van-button icon="share-o"></van-button>
        </van-col>
    </van-row>

    <!-- 评论Popup 弹出层 -->
    <van-popup v-model="showPopup" closeable position="bottom"
               :style="{ width: '750px', height: '60%', left: '50%', 'margin-left': '-375px' }">
        <!-- 评论回复列表 -->
        <van-list v-model="commentRepliesLoading" :finished="commentRepliesFinished" finished-text="没有更多了"
                  @load="onLoadCommentReplies">
            <van-row id="#comment-reply-view" type="flex" class="article-comment-reply"
                     v-for="(item, index) in commentReplies" :key="index">
                <van-col span="3">
                    <van-image round src="https://p3.pstatp.com/thumb/1480/7186611868" class="article-avatar"></van-image>
                </van-col>
                <van-col span="21">
                    <van-row type="flex" align="center" justify="space-between">
                        <van-col class="comment-author" v-html="item.authorName"></van-col>
                        <van-col>
                            <van-button round :icon="item.operation === 0 ? 'good-job' : 'good-job-o'" size="normal"
                                        @click="handleClickCommentReplyLike(item)">{{ item.likes || '' }}
                            </van-button>
                        </van-col>
                    </van-row>

                    <van-row>
                        <van-col class="comment-content" v-html="item.content"></van-col>
                    </van-row>
                    <van-row type="flex" align="center">
                        <!-- TODO: js计算时间差 -->
                        <van-col span="10" class="comment-time">
                            {{ item.createdTime | timestampToDateTime }}
                        </van-col>
                    </van-row>
                </van-col>
            </van-row>
        </van-list>
        <!-- 评论回复底部栏 -->
        <van-row type="flex" justify="space-around" align="center" class="comment-reply-bottom-bar">
            <van-col span="13">
                <van-field v-model="commentReplyValue" placeholder="写评论">
                    <template #button>
                        <van-button icon="back-top" @click="handleSaveCommentReply"></van-button>
                    </template>
                </van-field>
            </van-col>
            <van-col span="3">
                <van-button icon="comment-o"></van-button>
            </van-col>
            <van-col span="3">
                <van-button icon="star-o"></van-button>
            </van-col>
            <van-col span="3">
                <van-button icon="share-o"></van-button>
            </van-col>
        </van-row>
    </van-popup>
</div>

<!-- 引入 Vue 和 Vant 的 JS 文件 -->
<script src=" https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js">
</script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/vant.min.js"></script>
<!-- 引入 Axios 的 JS 文件 -->
<#--<script src="https://unpkg.com/axios/dist/axios.min.js"></script>-->
<script src="../../../plugins/js/axios.min.js"></script>
<!-- 页面逻辑 -->
<script src="../../../plugins/js/index.js"></script>
</body>

</html>

数据库content

image-20240904204510275

类型 和 值 ,text文本类型,一种样式, img图片类型,另一种样式

image-20240904204543622

上传基础css,js

tip:js(修改地址)

image-20240904205500233

image-20240904205358124

依次上传js和css文件

image-20240904205603690

新建测试类

感叹一下,牛逼,只有当文章修改时需要重新查询数据库,日常的无数次给用户查询都访问静态页面,大大减轻了数据库的压力,一张表存longtext内容,一张表存基础信息

思路

  1. 查库获取content(次数很少,只有新增和修改会查)

  2. 不为空则通过模板文件生成html。获取模板

  3. 填充数据模型,由于模板里的list content名字固定,这里到时候的map属性值也的是content,且由于数据库存字符串,其值要求对象,json转对象

  4. 使用stringwrite (out)将html文件暂存

  5. 上传minio时将(out)参数传入,不过这里需要字节流,将字符流转为string再获取字节即可,第二个参数为文章id加html

  6. 返回的路径设置到article表的static url字段(articleService根据文章id)值为url

package test;

import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
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.article.service.ApArticleService;
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.checkerframework.checker.units.qual.A;
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.IOException;
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 {

    // 注入template模板类
    @Autowired
    private Configuration configuration;
    // 文件上传类
    @Autowired
    private FileStorageService fileStorageService;
    @Autowired
    private ApArticleContentMapper apArticleContentMapper;
    @Autowired
    private ApArticleMapper apArticleMapper;

    @Test
    public void createStaticUrlTest() throws Exception {
        //1.获取文章内容
        ApArticleContent apArticleContent = apArticleContentMapper.selectOne(Wrappers.<ApArticleContent>lambdaQuery()
                .eq(ApArticleContent::getArticleId, 1383827787629252610L));
        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);


        }
    }
}

image-20240905083410773

标签:heima,org,private,2024,import,跟学,article,com,头条
From: https://blog.csdn.net/Zwwxd666/article/details/141915810

相关文章

  • 2024年 Windows Python 下载、安装教程,附详细图文
    大家好,今天为大家带来的是2024年WindowsPython下载、安装教程,附详细图文,适用于Python3所有版本,包括Python3.7,Python3.8,Python3.103.9,Python3.10等版本。希望对大家有所帮助Python目前已支持所有主流操作系统,在Linux,Unix,Mac系统上自带Python环境,一般默认装的是Py......
  • 2024年全国大学生数学建模竞赛A-E题思路解析+代码+论文
    2024年高教社杯全国大学生数学建模竞赛(以下简称国赛)将于9月5日晚6时正式开始。下文包含:2024国赛思路解析​、国赛参赛时间及规则信息说明、好用的数模技巧及如何备战数学建模竞赛C君将会第一时间发布选题建议、所有题目的思路解析、相关代码、参考文献、参考论文等多项资料,帮......
  • Transfusion: Predict the Next Token and Diffuse Images with One Multi-Modal Mode
    Transfusion:PredicttheNextTokenandDiffuseImageswithOneMulti-ModalModel(2024,8)PaperTODO:目前没有开源代码,实时关注一下officialcode,Meta的工作基本开源的.本文给出了一种新的T2I的方法.lucidrains的代码本质是将LLM的transformer和图像中的diffusion结......
  • 【Python使用】嘿马头条项目从到完整开发教程第8篇:缓存,多级缓存【附代码文档】
    本教程的知识点为:简介1.内容2.目标产品效果ToutiaoWeb虚拟机使用说明数据库理解ORM作用思考:使用ORM的方式选择数据库SQLAlchemy操作1新增2查询all()数据库分布式ID1方案选择2头条使用雪花算法(代码toutiao-backend/common/utils/snowflake)数据库Red......
  • Cinema 4D v2024 激活版下载与安装教程 (3D建模工具)
    安装步骤Cinema4Dv2024版:Cinema4D_2024_2024.3.0.zip1、下载解压后点击如下图运行安装2、选择安装路径,将C改成D,安装到D盘符3、安装中,耐心等待2-5分钟4、安装完取消勾选,点击完成,请先不要启动软件。5、将crack文件夹c4d_base.xdl64文件覆盖corelibs文件夹,如下图所示找......
  • 【计算机视觉前沿研究 热点 顶会】ECCV 2024中目标检测有关的论文
    整值训练和尖峰驱动推理尖峰神经网络用于高性能和节能的目标检测与人工神经网络(ANN)相比,脑激励的脉冲神经网络(SNN)具有生物合理性和低功耗的优势。由于SNN的性能较差,目前的应用仅限于简单的分类任务。在这项工作中,我们专注于弥合人工神经网络和神经网络在目标检测方面的性能......
  • 免费视频压缩软件下载?2024年最新15款好用的视频压缩工具推荐!
    做过短视频的朋友,肯定都知道大部分平台的视频大小都有限制,那么如何通过无损压缩,上传体积小、清晰度高的视频,成为了不少同学的锥心之痛!今天,俺就以一名剪辑师的身份,分享圈里人用的比较多的视频压缩工具,大部分都是免费哒,绝对可以让你的是制作如虎添翼!1、压缩宝官网:https://www.......
  • 2024.08.03米哈游秋招第一场
    1.数组价值米小游有一个长度为n的数组,其中第i个元素为ai。现在定义数组的价值是最大的相邻数字的乘积。例如数组为[3,5,1,2],相邻元素的乘积分别是35=15,51=5和1*2=2,则数组的价值是这些数字中的最大值,即15。现在米小游想要任选数组中的某两个相邻的元素进行交换(你必......
  • 2024.8
    1.ARC183DKeepPerfectlyMatched思考了一会后,发现答案是存在一个上界的:以重心\(r\)定根,一条边至多经过\(sz_i\)次。而这个东西一出来,就知道一定是能顶到的了,因为太典了。我们考虑,当且仅当,每次删除的两个叶子,都属于\(r\)的两颗不同子树,符合要求。而一次删除,怎么样才能让......
  • 学习日记- 2024.9.3
    1.上课情况Analog没怎么听,今天半天没找到AE的教学楼,到教室的时候已经没有座位了。电磁学上得太快了,自己回来学吧。2.复习2.1.Wireless-lec1第一课的学习目标:• Understandthebasicsofa:wirelesslinkandwirelesscell,thespectrumusageandwirelesssignals......