当前位置: 首页 > 编程日记 > 正文

Velocity Engine基础

一. velocity简介

回到顶部

1. velocity简介

Velocity是一个基于Java的模板引擎,可以通过特定的语法获取在java对象的数据 , 填充到模板中,从而实现界面和java代码的分离!
image

2. 应用场景

  • Web应用程序 : 作为为应用程序的视图, 展示数据。
  • 源代码生成 : Velocity可用于基于模板生成Java源代码
  • 自动电子邮件 : 网站注册 , 认证等的电子邮件模板
  • 网页静态化 : 基于velocity模板 , 生成静态网页

3. velocity 组成结构

image
Velocity主要分为app、context、runtime和一些辅助util几个部分。

  • app模块 : 主要封装了一些接口 , 暴露给使用者使用。主要有两个类,分别是Velocity(单例)和VelocityEngine。
  • Context模块 : 主要封装了模板渲染需要的变量
  • Runtime模块 : 整个Velocity的核心模块,Runtime模块会将加载的模板解析成语法树,Velocity调用mergeTemplate方法时会渲染整棵树,并输出最终的渲染结果。
  • RuntimeInstance类为整个Velocity渲染提供了一个单例模式,拿到了这个实例就可以完成渲染过程了。

二. 快速入门

1. 需求分析

使用velocity定义html模板 , 将动态数据填充到模板中 , 形成一个完整的html页面

2. 步骤分析

  1. 创建项目(maven)
  2. 引入依赖
  3. 定义模板
  4. 输出html

3. 代码实现

3.1 创建工程

image

3.2 引入坐标

<dependencies>
    <dependency>
        <groupid>org.apache.velocity</groupid>
        <artifactid>velocity-engine-core</artifactid>
        <version>2.2</version>
    </dependency>
    <dependency>
        <groupid>junit</groupid>
        <artifactid>junit</artifactid>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

3.3 编写模板

在项目resources目录下创建模板文件

    <meta charset="UTF-8">
    <title>Title</title>



hello , ${name} !

3.4 输出结果

@Test
public void test1() throws IOException {
    //设置velocity资源加载器
    Properties prop = new Properties();
    prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
    Velocity.init(prop);

    //创建Velocity容器
    VelocityContext context = new VelocityContext();
    context.put("name", "zhangsan");
    //加载模板
    Template tpl = Velocity.getTemplate("vms/demo1.vm", "UTF-8");

    FileWriter fw  = new FileWriter("D:\\work\\workspace\\velocity\\velocity_01\\src\\main\\resources\\html\\demo1.html");
    //合并数据到模板
    tpl.merge(context, fw);

    //释放资源
    fw.close();
}

4. 运行原理

Velocity解决了如何在后台程序和网页之间传递数据的问题,后台代码和视图之间相互独立,一方的修改不影响另一方 .

他们之间是通过环境变量(Context)来实现的,网页制作一方和后台程序一方相互约定好对所传递变量的命名约定,比如上个程序例子中的site, name变量,它们在网页上就是 n a m e , name , name,site 。

只要双方约定好了变量名字,那么双方就可以独立工作了。无论页面如何变化,只要变量名不变,那么后台程序就无需改动,前台网页也可以任意由网页制作人员修改。这就是Velocity的工作原理。
image

三. 基础语法

3.1 VTL介绍

Velocity Template Language (VTL) , 是Velocity 中提供的一种模版语言 , 旨在提供最简单和最干净的方法来将动态内容合并到网页中。简单来说VTL可以将程序中的动态数展示到网页中

VTL的语句分为4大类:注释 , 非解析内容 , 引用和指令

3.2 VTL注释

3.2.1 语法

1. 行注释

## 行注释内容

2. 块注释

#*
块注释内容1
块注释内容2
*#

3. 文档注释

#**
文档注释内容1
文档注释内容2
*#

3.2.1 示例

    <meta charset="UTF-8">
    <title>Title</title>


## 我是行注释

#*
* 我是块注释
* 呵呵呵
* *#

#**
* 我是文档注释
*
* *#
hello , ${name} !

3.3 非解析内容

所谓非解析内容也就是不会被引擎解析的内容。

3.3.1 语法

#[[
非解析内容1
非解析内容2 
]]#

3.3.2 示例

    <meta charset="UTF-8">
    <title>Title</title>



hello , ${name} !
    
<h1>非解析内容</h1>
#[[
直接输出的内容1
直接输出的内容2
${name}
]]#

3.4 引用

3.4.1 变量引用

引用语句就是对引擎上下文对象中的属性进行操作。语法方面分为常规语法($属性)和正规语法(${属性})。

语法
$变量名, 若上下文中没有对应的变量,则输出字符串"$变量名"
${变量名},若上下文中没有对应的变量,则输出字符串"${变量名}" 
$!变量名, 若上下文中没有对应的变量,则输出空字符串"" 
$!{变量名}, 若上下文中没有对应的变量,则输出空字符串""
示例
    <meta charset="UTF-8">
    <title>Title</title>


<h1>引用变量</h1>
常规语法 : $name
正规语法 : ${name}

## 如果获取的变量不存在, 表达式会原样展示 , 如果不想展示 , 可以使用 $!变量名
## 以下写法的含义代表么如果有变量, 那么获取变量值展示, 没有变量展示""
常规语法 : $!name
正规语法 : $!{name}

3.4.2 属性引用

语法
$变量名.属性, 	若上下文中没有对应的变量,则输出字符串"$变量名.属性"
${变量名.属性}	若上下文中没有对应的变量,则输出字符串"${变量名.属性}"
$!变量名.属性	若上下文中没有对应的变量,则输出字符串""
$!{变量名.属性}	若上下文中没有对应的变量,则输出字符串""
示例
    <meta charset="UTF-8">
    <title>Title</title>


<h1>引用属性</h1>
常规语法 : $user.username --- $user.password
正规语法 : ${user.username} --- ${user.password}

正规语法 : ${user.email} --- ${user.email}
正规语法 : $!{user.email} --- $!{user.email}

3.4.3 方法引用

方法引用实际就是指方法调用操作,关注点返回值参数 , 方法的返回值将输出到最终结果中

语法
$变量名.方法([入参1[, 入参2]*]?), 常规写法
${变量名.方法([入参1[, 入参2]*]?)}, 正规写法

$!变量名.方法([入参1[, 入参2]*]?), 常规写法
$!{变量名.方法([入参1[, 入参2]*]?)}, 正规写法
示例
    <meta charset="UTF-8">
    <title>Title</title>


<h1>引用属性</h1>
$str.split(" ")
${str.split(" ")}
$time.getTime()
${time.getTime()}

3.5 指令

指令主要用于定义重用模块、引入外部资源、流程控制。指令以 # 作为起始字符。

3.5.1 流程控制

#set

作用 : 在页面中声明定义变量

语法: #set($变量 = 值)

示例 :

    <meta charset="UTF-8">
    <title>Title</title>


<h1>set指令</h1>
#set($str = "hello world")
#set($int = 1)
#set($arr = [1,2])
#set($boolean = true)
#set($map = {"key1":"value1", "key2":"value2"})

## 在字符串中也可以引用之前定义过的变量
#set($str2 = "$str , how are you !")
#set($str3 = '$str , how are you !')
    

<h1>获取set指令定义的变量</h1>
${str}
${int}
${arr}
${boolean}
${map.key1}--${map.key2}
${str2}
${str3}
#if/#elseif/#else

作用 : 进行逻辑判断

语法 :

#if(判断条件)
  .........
#elseif(判断条件)
  .........
#else
  .........
#end 

示例 :

    <meta charset="UTF-8">
    <title>Title</title>


<h1>if/elseif/else指令</h1>
#set($language="PHP")

#if($language.equals("JAVA"))
    java开发工程师
#elseif($language.equals("PHP"))
    php开发工程师
#else
    开发工程师
#end
#foreach

作用 : 遍历循环数组或者集合

格式:

#foreach($item in $items)
    ..........
    [#break]
#end
> - $items : 需要遍历的对象或者集合
>   - 如果items的类型为map集合, 那么遍历的是map的value
> - $item : 变量名称, 代表遍历的每一项 
> - #break : 退出循环
> - 内置属性 : 
>   - $foreach.index  : 获取遍历的索引 , 从0开始
>   - $foreach.count : 获取遍历的次数 , 从1开始

示例 :

    <meta charset="UTF-8">
    <title>Title</title>



<h1>遍历数组</h1>
#foreach($str in $hobbies)
    ${foreach.index} -- ${str}  <br>
#end

<h1>变量对象集合</h1>

    #foreach($user in $users)
        
    #end
<table border="1px" align="center">
    <tbody><tr>
        <td>编号</td>
        <td>用户名</td>
        <td>密码</td>
        <td>邮箱</td>
        <td>年龄</td>
        <td>操作</td>
    </tr><tr>
            <td>${foreach.index}</td>
            <td>${user.username}</td>
            <td>${user.password}</td>
            <td>${user.email}</td>
            <td>${user.age}</td>
            <td>
                <a href="">编辑</a>
                <a href="">删除</a>
            </td>
        </tr></tbody></table>

<h1>遍历map集合</h1>
<h2>遍历值</h2>
 #foreach($value in $map)
     $value
 #end

<h2>遍历键值对</h2>
#foreach($entry in $map.entrySet())
    $entry.key -- $entry.value
#end

3.5.2 引入资源

#include

作用 : 引入外部资源 , 引入的资源不会被引擎所解析

语法 : #include(resource)

  • resource可以为单引号或双引号的字符串,也可以为**$变量**,内容为外部资源路径。
  • 注意 : 路径如果为相对路径,则以引擎配置的文件加载器加载路径作为参考

示例 :

    <meta charset="UTF-8">
    <title>Title</title>



#include("demo8.vm")
#parse

作用 : 引入外部资源 , 引入的资源将被引擎所解析

语法 : #parse(resource)

  • resource可以为单引号或双引号的字符串,也可以为**$变量**,内容为外部资源路径。
  • 注意 : 路径如果为相对路径,则以引擎配置的文件加载器加载路径作为参考系

示例 :

    <meta charset="UTF-8">
    <title>Title</title>



#parse("demo8.vm")
#define

作用 : 定义重用模块(不带参数)

语法 :

#define($模块名称)
	模块内容
#end

示例 :

    <meta charset="UTF-8">
    <title>Title</title>


<h1>定义模块</h1>
#define($table)
<table border="1px" align="center">
    <tbody><tr>
        <td>编号</td>
        <td>用户名</td>
        <td>密码</td>
        <td>邮箱</td>
        <td>年龄</td>
        <td>操作</td>
    </tr>
    <tr>
        <td>0</td>
        <td>zhangsan</td>
        <td>123456</td>
        <td>zhangsan@qq.com</td>
        <td>18</td>
        <td>
            <a href="">编辑</a>
            <a href="">删除</a>
        </td>
    </tr>
    <tr>
        <td>1</td>
        <td>lisi</td>
        <td>123456</td>
        <td>zhangsan@qq.com</td>
        <td>18</td>
        <td>
            <a href="">编辑</a>
            <a href="">删除</a>
        </td>
    </tr>
    <tr>
        <td>2</td>
        <td>wangwu</td>
        <td>123456</td>
        <td>zhangsan@qq.com</td>
        <td>18</td>
        <td>
            <a href="">编辑</a>
            <a href="">删除</a>
        </td>
    </tr>
    <tr>
        <td>3</td>
        <td>tianqi</td>
        <td>123456</td>
        <td>zhangsan@qq.com</td>
        <td>18</td>
        <td>
            <a href="">编辑</a>
            <a href="">删除</a>
        </td>
    </tr>
</tbody></table>
#end

<h1>使用模块</h1>
$table
$table
$table
#evaluate

作用 : 动态计算 , 动态计算可以让我们在字符串中使用变量

语法 : #evalute("计算语句")

示例 :

    <meta charset="UTF-8">
    <title>Title</title>


<h1>动态计算</h1>
#set($name = "over")

#evaluate("#if($name=='over') over  #else  not over #end")

#if($name=='over')
    over
#else
    not over
#end

3.5.3 宏指令

作用 : 定义重用模块(可带参数)

语法 :

定义语法

#macro(宏名 [$arg]?)
   .....
#end

调用语法

#宏名([$arg]?)

示例 :

    <meta charset="UTF-8">
    <title>Title</title>


<h1>定义宏</h1>
#macro(table $list)

    #foreach($item in $list)
    
    #end
<table border="1px">
    <tbody><tr>
        <td>编号</td>
        <td>用户名</td>
        <td>密码</td>
        <td>邮箱</td>
        <td>年龄</td>
        <td>操作</td>
    </tr><tr>
        <td>${foreach.count}</td>
        <td>${item.username}</td>
        <td>${item.password}</td>
        <td>${item.email}</td>
        <td>${item.age}</td>
        <td>
            <a href="">编辑</a>
            <a href="">删除</a>
        </td>
    </tr></tbody></table>
#end

<h1>调用宏</h1>
#table($users)

四. 综合案例

4.1 需求分析

在实际项目开发过程中, 编写基础的CRUD操作代码, 往往会花费我们大量的时间 , 而且这些CRUD代码的基础结构基本上是固定的 , 如果能有一个代码生成器 , 能够帮助我们把这些代码生成出来 , 我们就可以节省出大量的时间关注核心业务代码的开发, 大大提高了我们的开发效率 !

需求 : 使用velocity实现一个简单的代码生成器 , 生成项目开发过程中的基础CRUD代码

4.2 步骤分析

  1. 创建项目
  2. 导入依赖
  3. 编写模板
  4. 生成代码

4.3 代码实现

4.3.1 创建项目

image

4.3.2 导入依赖

<dependency>
    <groupid>org.apache.velocity</groupid>
    <artifactid>velocity-engine-core</artifactid>
    <version>2.2</version>
</dependency>

4.3.3 编写模板

一般我们的项目开发将项目分为三层 , 我们的代码生成器就基于传统的三层架构生成代码 , 所以我们需要为每一层的每一个类创建模板 , 所以需要有如下模板 :

  • Controller.java.vm : 控制层模板
  • Service.java.vm : 业务层接口模板
  • ServiceImpl.java.vm : 业务层实现模板
  • Dao.java.vm : 数据服务层模板(数据访问层基于通用Mpper实现)
Controller.java.vm
package ${package}.controller;

import ${package}.pojo.${className};
import ${package}.service.${className}Service;
import ${package}.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/${classname}")
public class ${className}Controller {

    @Autowired
    private ${className}Service ${classname}Service ;


    /**
     * 查询列表
     * @return
     */
    @RequestMapping("/list")
    public Result list(){
        List<${className}>  ${classname}s = null;
        try {
                ${classname}s = ${classname}Service.list();
            return Result.ok(${classname}s);
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error("查询数据异常");
        }
    }

    /**
     * 保存
     * @param ${classname}
     * @return
     */
    @RequestMapping("/save")
    public Result save(@RequestBody ${className} ${classname}){
        try {
                ${classname}Service.save(${classname});
            return Result.ok("新增数据成功");
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error("新增数据异常");
        }
    }

    /**
     * 更新
     * @param ${classname}
     * @return
     */
    @RequestMapping("/update")
    public Result update(@RequestBody ${className} ${classname}){
        try {
                ${classname}Service.update(${classname});
            return Result.ok("修改数据成功");
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error("修改数据异常");
        }
    }

    /**
     * 删除
     * @param ids
     * @return
     */
    @RequestMapping("/delete")
    public Result delete(@RequestBody Integer[] ids){
        try {
                ${classname}Service.delete(ids);
            return Result.ok("删除数据成功");
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error("删除数据异常");
        }
    }
}
Service.java.vm
package ${package}.service;

import com.itheima.pojo.${className};

import java.util.List;
import java.util.Map;

public interface ${className}Service {

    /**
     * 查询数据列表
     * @return
     */
    List<${className}> list();

    /**
     * 保存数据
     * @param ${classname}
     */
    void save(${className} ${classname});

    /**
     * 更新数据
     * @param ${classname}
     */
    void update(${className} ${classname});

    /**
     * 删除数据
     * @param ids
     */
    void delete(Integer[] ids);
}
ServiceImpl.java.vm
package ${package}.service.impl;

import ${package}.dao.${className}Dao;
import ${package}.pojo.${className};
import ${package}.service.${className}Service;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class ${className}ServiceImpl  implements ${className}Service {

    @Autowired
    private ${className}Dao ${classname}Dao ;

    public List<${className}> list() {
        return ${classname}Dao.selectAll();
    }

    public void save(${className} ${classname}) {
            ${classname}Dao.insert(${classname});
    }

    public void update(${className} ${classname}) {
            ${classname}Dao.updateByPrimaryKey(${classname});
    }

    public void delete(Integer[] ids) {
        Stream.of(ids).forEach(${classname}Dao::deleteByPrimaryKey);
    }
}
Dao.java.vm
package ${package}.dao;

import com.itheima.pojo.${className};
import tk.mybatis.mapper.common.Mapper;

public interface ${className}Dao extends Mapper<${className}> {
}

4.3.4 生成代码

我们可以封装一个生成代码的工具类 , 后期生成代码运行工具类即可

package com.itheima.utils;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 代码生成器   工具类
 */
public class GenUtils {

    private static String currentTableName;

    public static List<string> getTemplates() {
        List<string> templates = new ArrayList<string>();
        templates.add("vms/Controller.java.vm");
        templates.add("vms/Service.java.vm");
        templates.add("vms/ServiceImpl.java.vm");
        templates.add("vms/Dao.java.vm");

        return templates;
    }


    /**
     * 生成代码
     */
    public static void generatorCode(Map<string, object=""> data, List<string> templates, ZipOutputStream zip) {

        //设置velocity资源加载器
        Properties prop = new Properties();
        prop.put("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        Velocity.init(prop);

        //封装模板数据
        VelocityContext context = new VelocityContext(data);

        //获取模板列表
        for (String template : templates) {
            //渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(template, "UTF-8");
            tpl.merge(context, sw);

            try {
                //添加到zip
                zip.putNextEntry(new ZipEntry(getFileName(template, data.get("className").toString(), data.get("package").toString())));
                IOUtils.write(sw.toString(), zip, "UTF-8");
                IOUtils.closeQuietly(sw);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }



    /**
     * 获取文件名 , 每个文件所在包都不一样, 在磁盘上的文件名几路径也各不相同
     */
    public static String getFileName(String template, String className,String packageName) {
        String packagePath = "main" + File.separator + "java" + File.separator;
        if (StringUtils.isNotBlank(packageName)) {
            packagePath += packageName.replace(".", File.separator) + File.separator;
        }

        if (template.contains("Dao.java.vm")) {
            return packagePath + "dao" + File.separator + className + "Dao.java";
        }

        if (template.contains("Service.java.vm")) {
            return packagePath + "service" + File.separator + className + "Service.java";
        }

        if (template.contains("ServiceImpl.java.vm")) {
            return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java";
        }

        if (template.contains("Controller.java.vm")) {
            return packagePath + "controller" + File.separator + className + "Controller.java";
        }

        return null;
    }
}

4.3.5 运行测试

public class GeneratorCodeTest {

    public static void main(String[] args) throws IOException {
        Map<string,object> data = new HashMap<string,object>();
        data.put("className","Product");
        data.put("classname","product");
        data.put("package","com.itheima");

        File file = new File("D:\\Users\\Desktop\\code.zip");
        FileOutputStream outputStream = new FileOutputStream(file);
        ZipOutputStream zip = new ZipOutputStream(outputStream);

        GenUtils.generatorCode(data,GenUtils.getTemplates(),zip);

        zip.close();
    }
}

运行完毕之后, 可以看到输出路径下回自动生成一个压缩文件 , 解压之后将里面的代码copy到我们的项目之中即可 !

相关文章:

常见的七种加密算法及实现

**数字签名**、**信息加密** 是前后端开发都经常需要使用到的技术,应用场景包括了用户登入、交易、信息通讯、`oauth` 等等,不同的应用场景也会需要使用到不同的签名加密算法,或者需要搭配不一样的 **签名加密算法** 来达到业务目标。这里简单的给大家介绍几种常见的签名加密算法和一些典型场景下的应用。## 正文### 1. 数字签名**数字签名**,简单来说就是通过提供 **可鉴别** 的 **数字信息** 验证 **自身身份** 的一种方式。一套 **数字签名** 通常定义两种 **互补

每天一个摆脱if-else工程师的技巧——优雅的参数校验

在日常的开发工作中,为了程序的健壮性,大部分方法都需要进行入参数据校验。最直接的当然是在相应方法内对数据进行手动校验,但是这样代码里就会有很多冗余繁琐的if-else。throw new IllegalArgumentException("用户姓名不能为空");throw new IllegalArgumentException("性别不能为空");throw new IllegalArgumentException("性别错误");

计算机世界的“十六进制”为什么如此重要

在计算机世界中,十六进制扮演着不可或缺的角色。它以其紧凑的表示形式、与二进制的天然对应关系以及在各个领域的广泛应用,成为了计算机科学中的一把重要工具。总体而言,计算机需要十六进制并非偶然,它是一种为了更好地满足人类理解和处理数据的需求而产生的工具,为计算机科学的发展和应用提供了便利和支持。

面试官:如何实现10亿数据判重?

以 Java 中的 int 为例,来对比观察 BitMap 的优势,在 Java 中,int 类型通常需要 32 位(4 字节*8),而 BitMap 使用 1 位就可以来标识此元素是否存在,所以可以认为 BitMap 占用的空间大小,只有 int 类型的 1/32,所以有大数据量判重时,使用 BitMap 也可以实现。所以数据库去重显然是不行的。而使用集合也是不合适的,因为数据量太大,使用集合会导致内存不够用或内存溢出和 Full GC 频繁等问题,所以此时我们的解决方案通常是采用布隆过滤器来实现判重。

希腊字母表及读音

序号大写小写国际音标中文读音意义1Ααa:lf阿尔法角度;系数2Ββbet贝塔磁通系数;角度;系数3Γγga:m伽马电导系数(小写)4Δδdelt德尔塔变动;密度;屈光度5Εεep`silon伊普西龙对数之基数6Ζζzat截塔系数;方位角;阻抗;相对粘度;原子序数7Ηηeit艾塔磁滞系数;效率(小写)8Θθθit西塔温度;相位角9Ιιaiot约塔

第三方消息推送回调Java app消息推送第三方选择

由于最先集成的是极光,因此根据官方给的推送设备区分方式中,选择了使用标签tag来进行区分管理方式,其接口提供了设置和清理标签, 每次设置会覆盖上次的结果,当然这个需要和极光后台进行交互,是异步返回的。5、由于其接口没有使用免费和付费区分,对于接口的访问没有限制,从使用的情况来看,经常会出现不准的情况,并且设置标签的效果其实是添加,导致业务需要改变标签时,需要先清除在设置,然而接口又经常出问题,导致这部分也是一塌糊涂了;如果想使用不受免费版本限制特性的推送服务,可以联系平台提供的商务对接,购买付费版本。

为什么前后端都需要进行数据校验?

前端和后端各自的数据完整性校验是相辅相成的。前端校验可以提供即时反馈和优化用户体验,减轻后端服务器压力;后端校验是最终的安全防线,确保数据的完整性和一致性。通过前后端的数据完整性校验机制的结合,可以提供更可靠和安全的应用程序。

国内第三方移动推送对接调查:Android、IOS、Flutter,各种云推送、个推、极光、统一推送联盟

第三方移动推送对接,刚开始是移动端发起的。在开会讨论这个对接时,心里突然很迷茫,为什么要做第三方移动推送对接?我们自己为什么不能做移动推送?话说,项目里目前所使用的推送就是自己做的。但是在App离线情况下,消息就收不到了。想起来了,这是最最重要的问题,是为了在离线的情况下,App还能收到通知和消息。如果不是因为这个,这个对接可以不做。因为手机端的app层不出穷太多了,为了给手机省电,用户会主动把运行在前端的app给咔嚓掉…虽然咔嚓掉,但是在有信息的情况下,用户还是希望能够收到信息。

浏览器兼容video视频播放的多种方法&视频在浏览器播放格式,视频浏览器播放格式演示

对于老版本的IE可以通过HTML5shiv来使不支持HTML5的浏览器支持HTML新标签video和audio标签。主要解决HTML5提出的新的元素不被IE6/IE7/IE8识别,这些新元素不能作为父节点包裹子元素,且不能应用CSS样式。让CSS 样式应用在未知元素只需执行 document.createElement(elementName) 即可实现。html5shiv的工作原理也就是基于此。

抖音直播原理解析-如何在 Web 中播放 FLV 直播流

Media Source Extensions API(MSE)媒体源扩展 API 提供了实现无插件且基于 Web 的流媒体的功能,不同于简单的使用video元素,video元素对于开发者来说完全是一个黑盒,浏览器自己去加载数据,加载完了自己解析,解码再播放,这个过程中开发者无法进行任何操作。利用 MSE API 开发者可以自定义获取流媒体数据并且还可以对数据做一些操作。MSE 的兼容性如下图所示。可以发现 MSE 的兼容性还算可以,IE 11 都支持。

手机的ip地址是固定的吗,每个手机ip地址一样吗?

简单点说,路由器开启了“DHCP功能”,会自动给连接路由器网络的设备自动分配IP地址,这包括有线网络和无线网络;当设备开启了DHCP功能之后,在路由器开启了DHCP功能的前提下,就会自动接收路由器分配的IP地址,不需要用户手动设置静态IP地址,用通俗的话来形容“DHCP”,它可以省去用户手动设置IP地址的过程。

优秀的代码都是如何分层的——阿里开发规范

总的来说业务分层对于代码规范是比较重要,决定着以后的代码是否可复用,是否职责清晰,边界清晰。当然这种分层其实见仁见智, 团队中的所有人的分层习惯也不同,所以很难权衡出一个标准的准则,总的来说只要满足职责逻辑清晰,后续维护容易,就是好的分层。最后,如果你的团队有更好的分层,或者上面所描述的有什么错误的地方还请留言指正一下。

Vim 粘贴内容时全变成注释的问题

在使用vim粘贴代码时,会出现注释代码后面的代码全被注释的情况。在paste模式下进行复制粘贴就变得很正常了。

共享办公兴起背后:创新引擎或风口浪尖?

这些用户的需求和偏好也各不相同,有的需要灵活的租期和价格,有的需要专业的服务和支持,有的需要丰富的社交和活动,有的需要私密的空间和安全。共享办公的困境,还因为共享办公的市场空间和增长潜力有限,由于共享办公的核心资源是办公场地,而办公场地的供给受到土地、房产、政策等因素的制约,难以大规模扩张。共享办公的困境,其次是共享办公的用户体验和服务质量难以保证,由于共享办公空间的使用者多为中小企业和自由职业者,他们的需求和偏好各不相同,而共享办公空间往往缺乏个性化、智能化、便捷化的服务,难以满足用户的多元化需求。

科普:什么是“东数西算”?

数”指数据,“算”是算力,即对数据的处理能力,“东数西算”是通过构建数据中心、云计算、大数据一体化的新型算力网络体系,将东部算力需求有序引导到西部,优化数据中心建设布局,促进东西部协同联动。简单说就是把东边产生的数据拿到西边来储存,在西边进行数据分析和计算,然后再把结果传到东边。那“东数”为什么要“西算”呢?1.数据的储存和计算是需要大量的设备的,这东西就会占用大量的地,众所周知,东边经济发展的块,这占地成本很高,因此把储存和计算中心放到西部,可以降低成本,增加西部的就业和发展。

算法模板之栈图文详解

本文主要讲解栈的定义、用数组模拟栈的相关操作以及相关题目介绍,更多精彩内容等你来浏览。

算法模板之双链表图文详解

本文主要讲解双链表模板,文中带有超详细的图文讲解,希望对你的算法学习有一定的帮助。

算法模板之单链表图文讲解

本文主要讲解单链表模板,文中附有图文讲解,希望对你的算法学习有一定的帮助。

Java Comparator多属性排序

自然排序通常情况跟equals保持一致,e1,e2是类C的元素,如果e1.compareTo(e2) == 0 那么e1和e2 equals的结果也是true。因为有的时候它俩结果一致,比如刚好就只有一个属性排序的时候,但多数情况下,二者是不一致的。比如有多个排序属性的时候。有的时候获取的数据需要在内存排序,需要根据List中T对象的属性p1,p2,p3等进行排序,该怎么写呢?这种规则掌握了,管它几个属性排序,管它什么升序降序,通通解决。多个属性,按不同的升降序规则,就变的非常简单了,只需要在下面的。

【C++干货铺】初识模板

初识C++模板!!!函数模板&类模板!!!超详解!!!

储存容量单位:Bit, Byte, KB, MB, GB, TB , PB, EB, ZB, YB等的关系

有趣的是从 Wikipedia 看到的单位英文在「十进位」与「二进位」不同进制之间所使用的英文单字是不太一样的,例如我们常讲 30GB 会唸成 30 Gigabytes,不过正确的唸法应该是 Gibibytes 才对,不过大家都随便念、随便写,反正差不多、听的懂就好,我想唸过计算机概论的人自己都会知道 1GB = 1024 MB 吧,如果唸成 Gibibytes 搞不好还会被笑没知识!这样大的数据单位估计在未来的五年内是无法达到的,不过我相信假以时日人类的需求一定能够达到或者超越CB级。