拾壹博客拆解,页面元素替换(二)

页面元素替换

首先要做的当然是换成自己风格的站名和内容啦。

1、网站配置

跟踪前端代码后发现配置是来自后端接口,想着既然入库了,那应该有对应的管理页面吧,果然找到了,就是…演示账号不允许操作!那么接下来要干的事就很明显了,把这个用户搞定!
image.png

2、账号配置

切换到idea发现工作台存在一行报错,根据报错跳转到对应的代码,发现这鉴权方式没见过啊!赶紧面向百度编程。
image.png
Sa-token文档地址:https://sa-token.dev33.cn/
大概了解了下这个框架,简直是懒人福音啊x。然后发现页面上就有用户管理 + 修改密码,那么事情就变得简单了。
image.png

3、文件上传

因为预想中配置的文件服务器是minio,作者只附了本地和七牛两种方式,那么改造开始。

增加minio标签选项

全局搜索图片上传方式,找到对应绑定的字段,加上minio。PS:阿里oss原本也是没有的,但是跟踪后端代码发现字典值2对应的是阿里oss,就先加上了。
image.png

后端代码

跟踪/file/upload接口可以发现,后端是根据fileUploadWay 配置字段决定调用哪个上传策略。

private void getFileUploadWay() {
    
    
	strategy = FileUploadModelEnum.getStrategy(systemConfigService.getCustomizeOne().getFileUploadWay());
}

跟踪FileUploadModelEnum发现是个枚举类,那么先加上minio的枚举。
image.png
先在pom.xml引入minio

<!-- Minio -->
<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.2.2</version>
</dependency>

原作者的配置方式是在数据库加入字段,这种方式不太习惯,所以这边minio的配置都加入到配置文件中,后续使用**@Value**注入。

#============================Minio配置信息===================================
minio:
  url: http://ip:9000
  accessKey: minio账号
  secretKey: minio密码
  bucketName: 桶名称
  preurl: http://预览地址

随后仿造aliUploadStrategyImpl创建minio对应的service。

package com.shiyi.strategy.imp;

import com.shiyi.strategy.FileUploadStrategy;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectsArgs;
import io.minio.Result;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.velocity.shaded.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;
import java.util.stream.Collectors;

@Service("minioUploadStrategyImpl")
public class MinioUploadStrategyImpl implements FileUploadStrategy {
    
    

    private final Logger logger = LoggerFactory.getLogger(MinioUploadStrategyImpl.class);

    /**
     * 服务地址
     */
    @Value("${minio.url}")
    private String url;

    /**
     * 预览路径前缀
     */
    @Value("${minio.preurl}")
    private String preurl;

    /**
     * 用户名
     */
    @Value("${minio.accessKey}")
    private String accessKey;

    /**
     * 密码
     */
    @Value("${minio.secretKey}")
    private String secretKey;

    /**
     * 存储桶名称
     */
    @Value("${minio.bucketName}")
    private String bucketName;

    private static MinioClient client = null;

    @PostConstruct
    private void init(){
    
    
        client = MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build();
    }

    @Override
    public String fileUpload(MultipartFile file,String suffix) {
    
    
        String fileName = null;
        try {
    
    
        	String extension = FilenameUtils.getExtension(file.getOriginalFilename());

            fileName = DateFormatUtils.format(new Date(), "yyyy/MM/dd") + "/" + UUID.randomUUID() + "." + extension;
            PutObjectArgs args = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(fileName)
                    .stream(file.getInputStream(), file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build();
            client.putObject(args);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

        return preurl + "/" + bucketName + "/" + fileName;
    }

    /**
     * 删除文件 -- minio
     *
     * @param key   文件url
     * @return      ResponseResult
     */
    @Override
    public Boolean deleteFile(String ...key) {
    
    
        if (key.length > 0) {
    
    
            //批量删除
            Iterable<DeleteObject> deleteObjects = Arrays.stream(key).map(s -> new DeleteObject(s)).collect(Collectors.toList());

            Iterable<Result<DeleteError>> results = client.removeObjects(
                    RemoveObjectsArgs.builder()
                            .bucket(bucketName)
                            .objects(deleteObjects)
                            .build()
            );

            for (Result<DeleteError> result : results) {
    
    
                try {
    
    
                    result.get();
                } catch (Exception e) {
    
    
                    logger.error(e.getMessage());
                    e.printStackTrace();
                }
            }
        }

        return true;
    }

}

先在入口添加一下注解,再使用swagger调用测试,PS:记得先登录
![image.png](https://img-blog.csdnimg.cn/img_convert/25de1564a7752823ed66d8d1f751c3fb.png#averageHue=#2c2c2b&clientId=u5ccd04dd-8879-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=199&id=u6c178023&margin=[object Object]&name=image.png&originHeight=199&originWidth=2033&originalType=binary&ratio=1&rotation=0&showTitle=false&size=57089&status=done&style=none&taskId=u7ed14748-0b8e-4b09-b099-6807f1e7572&title=&width=2033)
image.png

文件中间表

为啥要用中间表呢,主要是想保护minio的端口。上传和下载都通过代码进行,就不能通过文件层级猜到别的文件路径。以及防止minio突然暴露什么漏洞。【当然如果是项目上用这个才不管呢!】

  1. 建表语句
CREATE TABLE `tb_files` (
  `id` bigint(20) NOT NULL COMMENT '主键id',
  `user_id` bigint(20) DEFAULT NULL COMMENT '用户id',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `preview_file` varchar(128) DEFAULT NULL COMMENT '文件minio地址',
  `file_name` varchar(512) DEFAULT NULL COMMENT '原文件名称',
  `content_type` varchar(50) DEFAULT NULL COMMENT '文件类型',
  `is_static` tinyint(1) DEFAULT '0' COMMENT '是否静态资源',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='文件表';
  1. 随便抄一个代码生成器
/**
 * 代码生成器
 */
public class CodeGenerator {
    
    

    public static void main(String[] args) {
    
    
        // 1、创建代码生成器
        AutoGenerator mpg = new AutoGenerator();
        // 2、全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");

        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("dingx");
        gc.setOpen(false); //生成后是否打开资源管理器
        gc.setFileOverride(false); //重新生成时文件是否覆盖
        /*
         * mp生成service层代码,默认接口名称第一个字母有 I
         * UcenterService
         * */
        gc.setServiceName("%sService"); //去掉Service接口的首字母I
        gc.setIdType(IdType.ASSIGN_ID); //主键策略
        gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型
        gc.setSwagger2(true);//开启Swagger2模式
        mpg.setGlobalConfig(gc);

        // 3、数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://ip:port/schema?serverTimezone=GMT%2B8");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("pwd");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);

        // 4、包配置
        PackageConfig pc = new PackageConfig();
//        pc.setModuleName(scanner("模块名")); //模块名
        pc.setParent("com.shiyi");
        pc.setController("controller");
        pc.setEntity("entity");
        pc.setService("service");
        pc.setMapper("mapper");
        mpg.setPackageInfo(pc);

        // 5、策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setInclude(scanner("表名"));
        strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略
        strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略
        strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain =true) setter链式操作
        strategy.setRestControllerStyle(true); //restful api风格控制器
        strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符
        mpg.setStrategy(strategy);

        // 6、执行
        mpg.execute();
    }

    private static String scanner(String tip) {
    
    
        Scanner scanner = new Scanner(System.in);
        System.out.println(("请输入" + tip + ":"));
        if (scanner.hasNext()) {
    
    
            String ipt = scanner.next();
            if (StringUtils.isNotBlank(ipt)) {
    
    
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }
}
  1. 改造上传代码
private final TbFilesService tbFilesService;

@Override
public String fileUpload(MultipartFile file,String suffix) {
    
    
    String fileName;
    TbFiles tbFile = null;
    try {
    
    
        String extension = getExtension(file);

        fileName = DateFormatUtils.format(new Date(), "yyyy/MM/dd") + "/" + UUID.randomUUID() + "." + extension;

        //保存上传文件记录
        tbFile = new TbFiles(file.getOriginalFilename(), fileName, file.getContentType());
        if (!tbFilesService.save(tbFile)){
    
    
            throw new RuntimeException("插入文件失败");
        }

        PutObjectArgs args = PutObjectArgs.builder()
                .bucket(bucketName)
                .object(fileName)
                .stream(file.getInputStream(), file.getSize(), -1)
                .contentType(file.getContentType())
                .build();
        client.putObject(args);
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }

    return preurl + "/" + tbFile.getId();
}
  1. 增加预览接口
public class TbFilesController {
    
    

    private final TbFilesService filesService;

    private final MinioUploadStrategyImpl minioUploadStrategy;

    /**
     * 预览
     * @param id
     * @return
     */
    @SaIgnore
    @GetMapping("/preview/{id}")
    @ResponseBody
    public ResponseEntity<StreamingResponseBody> preview(@PathVariable Long id){
    
    
        TbFiles file = filesService.getDetail(id);

        //设置头文件Content-type
        HttpHeaders headers = new HttpHeaders();

        // 发送给客户端的数据
        // 设置编码
        if (StringUtils.isNotBlank(file.getContentType())) {
    
    
            headers.setContentType(MediaType.valueOf(file.getContentType()));
        }

        //构造返回体
        return ResponseEntity.ok()
                .headers(headers)
                .body(outputStream -> {
    
    
                    try (InputStream inputStream = minioUploadStrategy.downloadFile(file.getPreviewFile())){
    
    
                        IOUtils.copy(inputStream, outputStream);
                    } catch (Exception e){
    
    
                        e.printStackTrace();
                    }
                });
    }
}

这里遇到的坑:
1)使用了ResponseEntity作为返回对象,使用HttpServletResponse的话,Content-type变更了也会被Spring框架自动更改为application/json。查找资料的时候看到很多使用**@GetMappingproduces属性,但是这样就固定了Content-type的内容。
2)不能使用下载的方式获取预览流,
标签中放入地址后虽然接口调用成功了,但是图是裂开的。
3)接口校验忽略接口 @SaIgnore 这个注解是sa-token 1.29版本没有的。这里升级到了
1.32版本。当然也可以改WebMvcConfig文件中的sa-token**拦截器。

  1. 功能测试

上传后查看数据库,已经入库。
image.png
调用preview方法
image.png

  1. 一个警告
!!!
An Executor is required to handle java.util.concurrent.Callable return values.
Please, configure a TaskExecutor in the MVC config under "async support".
The SimpleAsyncTaskExecutor currently in use is not suitable under load.
-------------------------------
Request URI: '/dingx/data/files/preview/1594875366335397890'
!!!

老实说,写了那么久代码第一次遇到warning提示。。
大意就是:默认的SimpleAsyncTaskExecutor已不适用,请自定义一个TaskExecutor。那就加呗,WebMvcConfig加入下列代码。

@Bean
public ThreadPoolTaskExecutor mvcTaskExecutor() {
    
    
    ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
    taskExecutor.setCorePoolSize(100);
    taskExecutor.setMaxPoolSize(100);
    return taskExecutor;
}

@Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
    
    
    configurer.setTaskExecutor(mvcTaskExecutor());
}

Nginx强化配置

nginx缓存

proxy_cache_path /root/cache levels=1:2 keys_zone=xd_cache:10m max_size=1g inactive=60m use_temp_path=off;

server {
    
    

    location /{
    
    
        ... 
        proxy_cache xd_cache;
        proxy_cache_valid 200 304 10m;
        proxy_cache_valid 404 1m; 
        proxy_cache_key $host$uri$is_args$args;
        add_header Nginx-Cache "$upstream_cache_status";
    }
}
配置讲解:
  1. /root/cache:本地路径,用来设置Nginx缓存资源的存放地址
  2. levels=1:2 :默认所有缓存文件都放在上面指定的根路径中,可能影响缓存的性能,推荐指定为 2
  3. 级目录来存储缓存文件;1和2表示用1位和2位16进制来命名目录名称。第一级目录用1位16进制命名,如a;第二级目录用2位16进制命名,如3a。所以此例中一级目录有16个,二级目录有16*16=256个,总目录数为16256=4096个。
  4. 当levels=1:1:1时,表示是三级目录,且每级目录数均为16个
  5. key_zone:在共享内存中定义一块存储区域来存放缓存的 key 和 metadata
  6. max_size :最大 缓存空间, 如果不指定会使用掉所有磁盘空间。当达到 disk 上限后,会删除最少使用的 cache
  7. inactive:某个缓存在inactive指定的时间内如果不访问,将会从缓存中删除
  8. proxy_cache_valid:配置nginx cache中的缓存文件的缓存时间,proxy_cache_valid 200 304 2m 对于状态为200和304的缓存文件的缓存时间是2分钟
  9. use_temp_path:建议为 off,则 nginx 会将缓存文件直接写入指定的 cache 文件中
  10. proxy_cache:启用proxy cache,并指定key_zone,如果proxy_cache off表示关闭掉缓存
  11. add_header Nging-Cache “$upstream_cache_status”:用于前端判断是否是缓存,miss、hit、expired(缓存过期)、updating(更新,使用旧的应答),还原nginx配置,只保留upstream模块
注意:
  1. nginx缓存过期影响的优先级进行排序为:inactvie > 源服务器端Expires/max-age > proxy_cache_valid
  2. 如果出现 Permission denied 修改nginx.conf,将第一行修改为 user root
  3. 默认情况下GET请求及HEAD请求会被缓存,而POST请求不会被缓存,并非全部都要缓存,可以过滤部分路径不用缓存

image.png

vue项目部署至nginx,路由404

查看官网推荐配置,cv一份。
image.png

文章SEO

先在百度搜索资源站配置好自己的网站:http://data.zz.baidu.com/linksubmit/index
找到普通收录,在配置文件中增加配置项

baidu:
  url: http://data.zz.baidu.com/urls?site=blog.dinganwang.top&token=
  sourceurl: https://blog.dinganwang.top/articles/

修改articleSeo方法。作者这边是用for循环实现的批量推送,emmmm老实说有点怪,所以稍微改了下。

@Value("${baidu.url}")
private String baiduUrl;

@Value("${baidu.sourceurl}")
private String sourceUrl;

private final static String SUCCESS = "success";
private final static String REMAIN = "remain";

/**
 *  文章百度推送
 * @return
 */
@Override
public ResponseResult articleSeo(List<Long> ids) {
    
    
    String param = "";

    for (Long id : ids) {
    
    
        param += sourceUrl + id + "\n";
    }

    HttpEntity<String> entity = new HttpEntity<>(param.trim(), createBdHeader());
    String res = restTemplate.postForObject(baiduUrl, entity, String.class);
    JSONObject JO = JSONObject.parseObject(res);
    if (JO.getInteger(SUCCESS) > 0){
    
    
        return ResponseResult.success("成功推送【" + JO.getInteger(SUCCESS) + "】条,剩余量【" + JO.getInteger(REMAIN) + "】条");
    }else {
    
    
        return ResponseResult.error("推送失败!");
    }
}

/**
 * 构造百度seo头文件
 * @return
 */
private static HttpHeaders createBdHeader(){
    
    
    HttpHeaders headers = new HttpHeaders();
    headers.add("Host", "data.zz.baidu.com");
    headers.add("User-Agent", "curl/7.12.1");
    headers.add("Content-Length", "83");
    headers.add("Content-Type", "text/plain");
    return headers;
}

第二天能够查看头一天的推送情况。
image.png

猜你喜欢

转载自blog.csdn.net/qq_16253859/article/details/128146435