0%

项目搭建相关问题

文件上传下载

  • -需要直接的路径文件下载(静态资源访问等)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    @Configuration
    public class MvcConfig {
    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
    return new WebMvcConfigurer() {
    /**
    * 首页设置
    * @param registry
    */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    registry.addViewController("/").setViewName("html/index");
    registry.addViewController("/index.html").setViewName("html/index");
    }

    /**
    * 静态资源虚拟地址映射
    * 文件上传读取相关
    * @param registry
    */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
    //获取jar包物理路径
    ApplicationHome ah = new ApplicationHome(getClass());
    File jarFile = ah.getSource();
    String filePath = jarFile.getParentFile().getPath() + "/upload";
    System.out.println("初始化文件上传路径:" + filePath);
    //添加静态文件资源与实际文件路径之间的映射(app_file换成自定义路径)
    registry.addResourceHandler("/app_file/**")
    .addResourceLocations("file:" + filePath + "/") ;
    }
    };
    }
    }

    之后我们就可以通过ip:端口/app_file/文件路径来访问对应的文件资源了

  • 服务器直接读取文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    public class FileService implements InitializingBean{
    private static String FILE_PATH;

    //这里不能直接在static代码中编写方法,因为getClass()方法会报空指针
    //实现InitializingBean的afterPropertiesSet方法,注入FILE_PATH
    @Override
    public void afterPropertiesSet() throws Exception {
    //获取jar包物理路径
    File jarFile = null;
    String filePath = null;
    try {
    ApplicationHome ah = new ApplicationHome(getClass());
    jarFile = ah.getSource();
    filePath = jarFile.getParentFile().getPath() + "/upload/";
    FILE_PATH = filePath;
    System.out.println("初始化文件上传路径:" + filePath);
    } catch (NullPointerException e) {
    // 运行单元测试时ApplicationHome(getClass())会报空指针异常
    }
    }

    public String getContent(String location) {
    StringBuilder builder = new StringBuilder();
    location = FILE_PATH + location;
    try {
    contentFile = ResourceUtils.getFile(location);
    } catch (FileNotFoundException e) {
    System.out.println("文件不存在,location:" + location);;
    }
    //流操作使用try-with-resource方式,更安全
    try (BufferedReader bufferedReader = new BufferedReader(new FileReader(contentFile))) {
    String tmpLine;
    int i = 2;
    while ((tmpLine = bufferedReader.readLine()) != null) {
    if (i > 0) {
    if (tmpLine.equals("---"))
    i--;
    } else if (!tmpLine.equals("<!--more-->")) {
    builder.append(tmpLine);
    builder.append("\n");
    }
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    content = builder.toString();
    return content;
    }
    }
  • 服务器接收上传文件

    直接使用CommonsMultipartFile.transferTo()会有打jar包保存文件不兼容问题,

    这里通过FileUtils.copyInputStreamToFile()来进行实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    @Controller
    @RequestMapping("/file")
    public class FIleController {
    //最大限制1M
    private static final Long FILE_SIZE_LIMIT = 1L * 1024L * 1024L;
    @Autowired
    private FIleService fIleService;

    /**
    * 数据和文件必须异步上传
    * 上传文件早于保存数据
    * @param file
    * @param params
    * @return
    * @throws IOException
    */
    @RequireToken
    @RequestMapping(value = "/upload", method = RequestMethod.POST)
    @ResponseBody
    public JsonObject upload(@RequestParam("file") MultipartFile file, @RequestParam Map<String, Object> params) throws IOException {
    JsonObject res = new JsonObject();
    if (!verifyFile(file, res)) {
    //验证不通过
    return res;
    }
    String uploadPath = fIleService.upload(file, blogId);
    res.put("uploadPath", uploadPath);
    return res;
    }

    /**
    * 验证是否通过
    * @param res 这里如果检验不通过会在res中设置status和message
    * @return 检验结果
    */
    private Boolean verifyFile(MultipartFile file, JsonObject res) {
    if (file.getSize() > FILE_SIZE_LIMIT) {
    res.setStatus(false);
    res.setMessage("文件不能超过最大值1M!");
    return false;
    }
    String originName = file.getOriginalFilename();
    //这里需要注意转义问题
    String[] names = originName.split("\\.");
    String lastName = names[names.length - 1];
    if (!"md".equals(lastName) && !"markdown".equals(lastName)) {
    res.setStatus(false);
    res.setMessage("文件类型不正确!请上传markdown文件");
    return false;
    }
    return true;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //FileService中的方法
    public String upload(MultipartFile file, String blogId) throws IOException {
    Date date = new Date();
    int year = date.getYear();
    int month = date.getMonth();
    //年月子文件夹路径前缀
    String datePath = "/" + (1900 + year) + "-" + (1 + month);
    File realPath = new File(FILE_PATH + datePath);
    if (!realPath.exists()) {
    realPath.mkdir();
    }
    String blogPath = realPath + "/" + blogId;
    //保存文件
    FileUtils.copyInputStreamToFile(file.getInputStream(), new File(blogPath));
    System.out.println("上传文件路径:" + blogPath);
    return "public/posts" + datePath + "/" + blogId;
    }

Jackson序列化相关

时间转换

虽然jdk8提供了新的日期api,但是一般使用基础的java.util.Date或者java.util.TimeStamp就足够了

这里注意不要将java.util.Date和java.sql.Date搞混,后者是前者的子类,toString时只展示日期,同理还有java.util.Timestamp和java.sql.TimeStamp

我在项目中所使用的是java.util.Date,在格式化日期显示方面,可以选择使用下面方式手动进行转换:

1
2
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = simpleDateFormat.format(date);

但这种方式需要在每个调用的位置都手动赋值,并且需要构建新的传输对象VO

这里推荐使用Jackson包含的注解@JsonFormat来实现

jackson依赖已经由springboot自动引入

  • 在后端服务器响应日期数据时,只需要在实体类的属性上加上@JsonFormat,Jackson在序列化时就会自动帮我们完成日期格式转换

    1
    2
    3
    4
    5
    class Entity {
    private Long id;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;
    }
  • 在接收前端日期数据时使用@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")来接收参数,可以自动转换成Date实体类

    由于时间一般由服务器自行生成,我在项目中并没有实际用到这个注解

Long和String的转换

众所周知,服务器向前端直接传输Long类型数据时,如果Long数据的长度较长,可能会造成精度丢失

因此一般服务端需要将Long类型转换成String类型进行传输,以防止精度丢失

这里同样推荐使用Jackson的统一配置进行实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
public class JacksonConfig {

/**
* Jackson全局转化long类型为String,解决jackson序列化时long类型缺失精度问题
* @return Jackson2ObjectMapperBuilderCustomizer 注入的对象
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
Jackson2ObjectMapperBuilderCustomizer cunstomizer = new Jackson2ObjectMapperBuilderCustomizer() {
@Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.serializerByType(Long.TYPE, ToStringSerializer.instance);
jacksonObjectMapperBuilder.serializerByType(Long.class, ToStringSerializer.instance);

}
};
return cunstomizer;
}
}

Thymeleaf小坑

在编写Html中的js代码时,如果需要通过[[$value]]来获取ModelAndView中的数据,需要在对应的script标签上添加th标签,如下所示:

1
2
3
4
<script type="text/javascript" th:inline="javascript">
let detail = [[${detail}]];
let articleContent = [[${articleContent}]];
</script>

否则js会直接转义替换string造成页面代码错误

统一Ajax返回对象

这里自定义了用于返回服务器数据的对象传输类,方便统一进行管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.lan5th.blog.utils;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
* @author lan5th
* @date 2022/6/23 21:40
*/
public class JsonObject implements Serializable {
private static final Long serialVersionUID = 7574078101944305355L;
private Boolean status;
private String message;
private Map<String, Object> data;
public JsonObject() {
this.data = new HashMap<>();
this.status = true;
}

public void put(String key, Object data) {
this.data.put(key, data);
}

public Object get(String key) {
return data.get(key);
}

public void setMessage(String message) {
this.message = message;
}

public void setStatus(Boolean status) {
this.status = status;
}

public boolean getStatus() {
return this.status;
}

public String getMessage() {
return this.message;
}

/**
* 只留给jackson使用
* @return
*/
public Map<String, Object> getData() {
return this.data;
}

public void setData(Map<String, Object> data) {
this.data = data;
}
}