SpringMvc 时间非国内系统部署大坑相差 8 小时

最近发现一个奇怪的现象,在国外 Linux 服务器上部署 Java 项目时间与国内 /Windows 上部署时间不一致,我用的是 docker 部署,因此在 Linux 宿主机与 docker 容器中时区都做了更改,但是还是有问题,存在八小时的时差,经多次尝试可以使用以下方式解决:@ResponseBody 注解默认使用 jackson 解析,因此我们可以配置其参数。对于 SpringBoot,在 spring 下面配置 jackson 相关属性统一处理。spring:#     设置 jackson 时区  +8  一般系统时间是正确就不需要(json 时间)  jackson:    time-zone: GMT+8#     给返回时间格式化    date-format: yyyy-MM-dd HH:mm:ssSpringMVC 同理 <mvc:annotation-driven>        <!--  处理请求时返回 json 字符串的中文乱码问题  -->        <mvc:message-converters register-defaults="true">            <bean class="org.springframework.http.converter.StringHttpMessageConverter">                <constructor-arg value="UTF-8" />            </bean>            <!-- 处理时区问题 -->            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">                <property name="objectMapper">                    <bean class="com.fasterxml.jackson.databind.ObjectMapper">                        <!--  处理 responseBody  里面日期类型  -->                        <property name="dateFormat">                            <bean class="java.text.SimpleDateFormat">                                <constructor-arg type="java.lang.String" value="yyyy-MM-dd HH:mm:ss" />                            </bean>                        </property>                        <!--  时区指定  -->                        <property name="timeZone" value="GMT+8" />                        <!--  为 null 字段时不显示  -->                        <property name="serializationInclusion">                            <value type="com.fasterxml.jackson.annotation.JsonInclude.Include">NON_NULL</value>                        </property>                    </bean>                </property>           </bean>        </mvc:message-converters>    </mvc:annotation-driven>当然也可对实体类中单独时间属性进行格式化,例如:@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")private Date creatTime;以上是对返回的 json 进行时区设置,接下来对代码中现在时间操作进行时区获取,最简单的方法://     注意大写 HH 是 24 小时制   小写为 12 小时制    static final String TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";    static final String DATE_PATTERN = "yyyy-MM-dd";    /**     *  获取东 8 区当前时间     * @return     */    public static Date getDate() {        ZonedDateTime now = ZonedDateTime.now(ZoneId.of("+08:00"));        String formatNow = now.format(DateTimeFormatter.ofPattern(TIME_PATTERN));        Date parse = null;        try {            parse = new SimpleDateFormat(TIME_PATTERN).parse(formatNow);        } catch (ParseException e) {            e.printStackTrace();        }        return parse;    }    public static String getDateStr(){        ZonedDateTime now = ZonedDateTime.now(ZoneId.of("+08:00"));        return now.format(DateTimeFormatter.ofPattern(DATE_PATTERN));    } 但是最后发现前端居然还是差 8 小时,但查看 post 请求接收的数据是正确的!但是前端框架我也用了 js 格式化日期方法对时间进行格式化!解决这个问题最简单方法使用上述的后端进行格式化时间返回给前端即可,前端不必再进行时间格式化操作。其实也可以这样 @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 需要引入依赖<dependency>   <groupId>com.fasterxml.jackson.core</groupId>   <artifactId>jackson-databind</artifactId>   <version>2.11.2</version></dependency>

LayUI 时间格式时间戳格式化成指定时间字符串

LayUI 表格中有数据要进行显示时间,但后台传来的时间为时间戳格式怎么办?很简单,示例如下:{title:" 创建时间 ",field:"cratetime",sort: true,templet: function(res){   return layui.util.toDateString(res.cratetime, 'yyyy-MM-dd HH:mm:ss')}} 类似的,如果在 angularJS 上是这样:{{entity.creatTime|date:'yyyy-MM-dd hh:mm:ss'}}

docker 上部署 maven 项目

首先安装 docker,由于自己是 ubuntu,因此安装非常简单:// 删除旧版本sudo apt-get remove docker docker-engine docker-ce docker.io// 更新包索引sudo apt-get update// 直接安装 dockersudo apt install docker.io然后 docker search tomcat 可以搜索到 tomcat 相关 docker 镜像,我选的是第二个带 jdk8 版本的,通过 docker pull 版本号 下载下来 docker 镜像。然后创建镜像 docker run -it  -v /usr/local/myhtml:/opt/tomcat/webapps -p 80:8080 --name=mytomcat  镜像名: 版本或镜像 ID /bin/bash 这里将 linux 主机 /usr/local/myhtml 目录与容器中的目录相映射了,- p 前面是本机端口,8080 是容器中端口 安装 Maven,用 wget 命令在 http://maven.apache.org/download.cgi 页面进行下载最新的 Maven(例如:http://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/3.6.1/binaries/apache-maven-3.6.1-bin.tar.gz),移动解压到指定目录,例如:sudo mv apache-maven-3.5.3-bin.tar.gz /usr/local/sudo tar -zxvf apache-maven-3.5.3-bin.tar.gz  sudo rm -rf apache-maven-3.5.3-bin.tar.gz编辑环境变量sudo vi /etc/profilesudo vi ~/.bashrc    // 与上面功能一致在最后两行中添加:export M2_HOME=/usr/local/apache-maven-3.5.3export PATH=${M2_HOME}/bin:$PATH保存退出后刷新下配置:source /etc/profilesource ~/.bashrc    // 同上最后用 mvn - v 试试是否能正常显示版本号 上传程序到 websapp 后如果是 zip 格式可以用 unzip FileName.zip 解压 如果是 springboot 程序忘了指定端口号,可以在启动时候指定,如:java -jar bootsample. jar -- server.port=9000更改 Linux 服务器时区(如果部署在 docker 中则需要改 docker 的时间),执行命令:sudo ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime然后重启服务器:sudo reboot详细可参见 亚马逊相关

SpringMVC 上传方式与异常处理

SpringMVC 文件上传 所需依赖<dependency>    <groupId>commons-fileupload</groupId>    <artifactId>commons-fileupload</artifactId>    <version>1.3.1</version></dependency><dependency>    <groupId>commons-io</groupId>    <artifactId>commons-io</artifactId>    <version>2.4</version></dependency>传统方式上传文件 通用上传页<h3> 文件上传 </h3><form action="user/fileupload" method="post" enctype="multipart/form-data">选择文件:<input type="file" name="upload"/><br/><input type="submit" value=" 上传文件 "/></form>从 request 中获取文件    @RequestMapping("/fileupload")    public String fileuoload1(HttpServletRequest req) throws Exception {        StandardMultipartHttpServletRequest request = (StandardMultipartHttpServletRequest) req;        System.out.println(" 文件上传...");        //  使用 fileupload 组件完成文件上传        //  上传的位置   获取最大的目录   即 webapp/WebRoot 目录        String path = request.getSession().getServletContext().getRealPath("/uploads/");        //  判断,该路径是否存在        File file = new File(path);        if (!file.exists()) {            //  创建该文件夹            file.mkdirs();        }        //  解析 request 对象,获取上传文件项        DiskFileItemFactory factory = new DiskFileItemFactory();        ServletFileUpload upload = new ServletFileUpload(factory);        //  解析 request        List<FileItem> items = upload.parseRequest(request);        //  遍历        for (FileItem item : items) {            //  进行判断,当前 item 对象是否是上传文件项            if (item.isFormField()) {                //  说明普通表单向            } else {                //  说明上传文件项                //  获取上传文件的名称                String filename = item.getName();                //  把文件的名称设置唯一值,uuid                String uuid = UUID.randomUUID().toString().replace("-", "");                filename = uuid + "_" + filename;                //  完成文件上传                item.write(new File(path, filename));                //  删除临时文件(大于 10k 的都会生成临时文件   因此要删除)                item.delete();            }        }        return "success";    }SpringMVC 方式上传 文件解析器配置springmvc.xml 配置文件解析器:<!--  配置文件解析器对象,要求 id 名称必须是 multipartResolver  不能改 --><bean id="multipartResolver"class="org.springframework.web.multipart.commons.CommonsMultipartResolver"><property name="maxUploadSize" value="10485760"/></bean>更多配置<!--  多部分文件上传  --><bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">     <property name="maxUploadSize" value="104857600" />     <property name="maxInMemorySize" value="4096" />     <property name="defaultEncoding" value="UTF-8"></property></bean>上述配置文件单位是 Byte,10M 就是 10x1024x1024 相乘的值 上传大小异常处理    <!-- 配置文件解析器对象 -->    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">        <property name="maxUploadSize" value="51200" />    </bean>    <!-- 1. 在文件上传解析时发现异常,此时还没有进入到 Controller 方法中  -->    <bean id="exceptionResolver" class= "org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">        <property name="exceptionMappings">            <props>                <!--  遇到 MaxUploadSizeExceededException 异常时,跳转到 error.jsp 页面  -->                <prop key= "org.springframework.web.multipart.MaxUploadSizeExceededException">/error </prop>            </props>        </property>    </bean>上述即文件超过 50k 就跳转错误页面,报错就不会执行 Controller 里的代码了。也可以自定义异常类,如下:/** *  自定义异常处理器类 */public class ExceptionHandler implements HandlerExceptionResolver{    /**     *  处理上传文件大小超过限制抛出的异常     */    @Override    public ModelAndView resolveException(HttpServletRequest req,            HttpServletResponse res, Object ob,Exception ex) {        ModelAndView mv=new ModelAndView();        // 判断异常类型,来跳转不同页面        if (ex instanceof MaxUploadSizeExceededException){             // 指定错误信息            mv.addObject("errormessage", " 上传文件过大 ");            // 设置跳转视图            mv.setViewName("userEdit");            return mv;        }          // 其他异常        return null;    }}然后将其加入 springmvc.xml 中即可 <bean class="com.cheng.exception.ExceptionHandler" /> 由于 Tomcat 问题,7.0 系列版本在上传大文件如果超过大小限制会直接页面连接断开。这时可以不用 SpringMVC 自带的解析器限制文件大小,而是通过代码实现。配置拦截器public class FileUploadInterceptor extends HandlerInterceptorAdapter {    private long maxSize;    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        // 判断是否文件上传        if(request!=null && ServletFileUpload.isMultipartContent(request)) {            ServletRequestContext ctx = new ServletRequestContext(request);            // 获取上传文件尺寸大小            long requestSize = ctx.contentLength();            if (requestSize > maxSize) {                // 当上传文件大小超过指定大小限制后,模拟抛出 MaxUploadSizeExceededException 异常                throw new MaxUploadSizeExceededException(maxSize);            }        }        return true;    }    public void setMaxSize(long maxSize) {        this.maxSize = maxSize;    }}配置解析器    <!--  配置文件上传类型解析器  multipartResolver-->    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />    <mvc:interceptors>        <mvc:interceptor>            <mvc:mapping path="/**"/>            <bean class="cn.cheng.controller.FileUploadInterceptor">                <!--  设定限制的文件上传大小  -->                <property name="maxSize" value="51200"/>            </bean>        </mvc:interceptor>    </mvc:interceptors>上传格式异常处理 第一种在 Controller 方法中限制,但每次都写太过于麻烦,第二种使用拦截器,下列是后者:public class FileTypeInterceptor extends HandlerInterceptorAdapter {    @Override    public boolean preHandle(HttpServletRequest request,                             HttpServletResponse response, Object handler)throws Exception {        boolean flag= true;        //  判断是否为文件上传请求        if (request instanceof MultipartHttpServletRequest) {            MultipartHttpServletRequest multipartRequest =                    (MultipartHttpServletRequest) request;            Map<String, MultipartFile> files =                    multipartRequest.getFileMap();            Iterator<String> iterator = files.keySet().iterator();            // 对多部件请求资源进行遍历            while (iterator.hasNext()) {                String formKey = (String) iterator.next();                MultipartFile multipartFile =                        multipartRequest.getFile(formKey);                String filename=multipartFile.getOriginalFilename();                // 判断是否为限制文件类型                if (! checkFile(filename)) {                    // 限制文件类型,请求转发到原始请求页面,并携带错误提示信息                    request.setAttribute("errormessage", " 不支持的文件类型!");                    request.getRequestDispatcher("/WEB-INF/pages/typeerror.jsp")                            .forward(request, response);                    flag= false;                }            }        }        return flag;    }    /**     *  判断是否为允许的上传文件类型,true 表示允许     */    private boolean checkFile(String fileName) {        // 设置允许上传文件类型        String suffixList = "jpg,gif,png,ico,bmp,jpeg";        //  获取文件后缀        String suffix = fileName.substring(fileName.lastIndexOf(".")                + 1, fileName.length());        if (suffixList.contains(suffix.trim().toLowerCase())) {            return true;        }        return false;    }}xml 配置文件配置拦截器,注意先后顺序    <mvc:interceptors>        <mvc:interceptor>            <!-- /** 表示所有 URL 和子 URL 路径  -->            <mvc:mapping path="/**"/>            <!--  配置自定义的文件上传类型限制拦截器  -->            <bean class="cn.cheng.controller.FileTypeInterceptor"/>        </mvc:interceptor>        <mvc:interceptor>            <mvc:mapping path="/**"/>            <bean class="cn.cheng.controller.FileUploadInterceptor">                <!--  设定限制的文件上传大小  -->                <property name="maxSize" value="512000000000"/>            </bean>        </mvc:interceptor>    </mvc:interceptors>这样无论传多大都不会错,但是格式不正确会被跳转到错误页面。拦截器返回 Json 数据 很多时候经常用 ajax 与 json 进行数据交互,因此并不一定每次出错都是跳转页面,返回 json 也很简单,直接写在拦截器 response 对象中即可response.setCharacterEncoding("UTF-8");response.setContentType("application/json; charset=utf-8");PrintWriter out = null ;try{    JSONObject res = new JSONObject();    res.put("success","false");    res.put("msg"," 上传文件类型错误 ");    out = response.getWriter();    out.append(res.toString());    return false;}catch (Exception e){    e.printStackTrace();    response.sendError(500);    return false;}当然我们也可将其封装成工具类public class SendMsgUtil {    /**     *  将某个对象转换成 json 格式并发送到客户端     * @param response     * @param obj     * @throws Exception     */    public static void sendJsonMessage(HttpServletResponse response, Object obj) throws Exception {        response.setContentType("application/json; charset=utf-8");        PrintWriter writer = response.getWriter();        writer.print(JSONObject.toJSONString(obj, SerializerFeature.WriteMapNullValue,                SerializerFeature.WriteDateUseDateFormat));        writer.close();        response.flushBuffer();    }}用 MultipartFile 的 transferTo代码:    @RequestMapping("/fileupload2")    public String fileuoload2(HttpServletRequest request, MultipartFile upload) throws Exception {        System.out.println("springmvc 文件上传...");        //  使用 fileupload 组件完成文件上传        //  上传的位置        String path = request.getSession().getServletContext().getRealPath("/uploads/");        //  判断,该路径是否存在        File file = new File(path);        if (!file.exists()) {            //  创建该文件夹            file.mkdirs();        }        //  说明上传文件项        //  获取上传文件的名称        String filename = upload.getOriginalFilename();        //  把文件的名称设置唯一值,uuid        String uuid = UUID.randomUUID().toString().replace("-", "");        filename = uuid + "_" + filename;        //  完成文件上传  new File 中做目录   又文件名        upload.transferTo(new File(path, filename));        return "success";    }其中用文件参数类型用 CommonsMultipartFile 亦可,但是这时必须给参数 @RequestParam("file")注解指定所传文件项!CommonsMultipartFile 的 getFileItem()    @RequestMapping("/fileupload2")    public String fileuoload2(HttpServletRequest request, @RequestParam("upload") CommonsMultipartFile upload) throws Exception {        System.out.println("springmvc 文件上传...");        //  使用 fileupload 组件完成文件上传        //  上传的位置        String path = request.getSession().getServletContext().getRealPath("/uploads/");        //  判断,该路径是否存在        File file = new File(path);        if (!file.exists()) {            //  创建该文件夹            file.mkdirs();        }        //  说明上传文件项        //  获取上传文件的名称        String filename = upload.getOriginalFilename();        String uploadFileFileNameWithoutSpace = filename.replaceAll(" ", "");        File targetFile = new File(path + File.separator, uploadFileFileNameWithoutSpace);        // 判断目标文件存在就删除   其实只要保证文件名唯一就无需该操作        if (targetFile.exists()) {            targetFile.delete();        }        upload.getFileItem().write(targetFile);        return "success";    }其中 File.separator 等效于分隔符 "/",当然也可以拼接这个分隔符,按常理说 windows 系统路径分隔符是 \,使用时候需转义为\\,Linux 是/,因此 separator 可以对系统进行判断返回相应分隔符。 多文件上传    @RequestMapping("fileupload22")    public String springUpload(HttpServletRequest request) throws IllegalStateException, IOException {        long startTime = System.currentTimeMillis();        // 将当前上下文初始化给   CommonsMutipartResolver (多部分解析器)        //  上传的位置        String path = request.getSession().getServletContext().getRealPath("/uploads/");        CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver(                request.getSession().getServletContext());        // 检查 form 中是否有 enctype="multipart/form-data"        if (multipartResolver.isMultipart(request)) {            // 将 request 变成多部分 request            MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;            // 获取 multiRequest  中所有的文件名            Iterator iter = multiRequest.getFileNames();            while (iter.hasNext()) {                // 一次遍历所有文件                MultipartFile file = multiRequest.getFile(iter.next().toString());                if (file != null) {                    String filepath = path + "/" + file.getOriginalFilename();                    System.out.println(filepath);                    // 上传                    file.transferTo(new File(filepath));                }            }        }        long endTime = System.currentTimeMillis();        System.out.println(" 运行时间:" + (endTime - startTime) + "ms");        return "success";    }跨服务器上传 有专门的图片服务器 B,用户访问网站在 A 服务器,在 A 网站上传图片被转存到 B 服务器,A 服务器不作储存。首先我们需要两个 tomcat。依赖:<dependency>    <groupId>com.sun.jersey</groupId>    <artifactId>jersey-core</artifactId>    <version>1.18.1</version></dependency><dependency>    <groupId>com.sun.jersey</groupId>    <artifactId>jersey-client</artifactId>    <version>1.18.1</version></dependency>实例代码 创建端口不同的 tomcat 的 web 项目,在 WEB-INF 里建 uploads 文件夹,修改 web.xml 配置文件<web-app>  <display-name>Archetype Created Web Application</display-name>  <servlet>    <servlet-name>default</servlet-name>    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>    <init-param>      <param-name>debug</param-name>      <param-value>0</param-value>    </init-param>    <init-param>      <param-name>readonly</param-name>      <param-value>false</param-value>    </init-param>    <init-param>      <param-name>listings</param-name>      <param-value>false</param-value>    </init-param>    <load-on-startup>1</load-on-startup>  </servlet></web-app>代码:    @RequestMapping("/fileupload3")    public String fileuoload3(MultipartFile upload) throws Exception {        System.out.println(" 跨服务器文件上传...");        //  定义上传文件服务器路径        String path = "http://localhost:9090/uploads/";        //  说明上传文件项        //  获取上传文件的名称        String filename = upload.getOriginalFilename();        //  把文件的名称设置唯一值,uuid        String uuid = UUID.randomUUID().toString().replace("-", "");        filename = uuid + "_" + filename;        //  创建客户端的对象        Client client = Client.create();//         需要对文件名进行 URL 编码   若直接中文或空格上传会报错        filename = URLEncoder.encode(filename, "utf-8");        //  和图片服务器进行连接        WebResource webResource = client.resource(path + filename);        //  上传文件        webResource.put(upload.getBytes());        return "success";    }关于报错 409 可能原因是编译好的 web 目录下没有相应的例如以上的 uploads 文件夹,你项目的 web 目录即使有 ploads 文件夹文件夹但没一个文件,编译好后会自动忽略创建该目录,可在源码 web 目录文件夹内放任意文件即可。 403 可能是 web.xml 未按上述配置写入权限,另外如果以上代码文件名未 URL 编码,中文文件名文件上传亦会产生 403 错误。 文件上传注意事项  1、为保证服务器安全,上传文件应该放在外界无法直接访问的目录下,比如放于 WEB-INF 目录下。  2、为防止文件覆盖的现象发生,要为上传文件产生一个唯一的文件名。  3、为防止一个目录下面出现太多文件,要使用 hash 算法打散存储。  4、要限制上传文件的最大值。  5、要限制上传文件的类型,在收到上传文件名时,判断后缀名是否合法。