# 技术培训

# 1 前端

# 1.1 Vue技术

# 1.1.1 Vue组件通信(父传子、子传父、兄弟传值)

# (1)父组件传到子组件

父组件是通过props属性给子组件通信的
数据是单向流动 父—>子 (子组件中修改props数据,是无效的,会有一个红色警告)

  1. 父组件parent.vue代码如下:

  2. 子组件son代码如下:

    子组件接收到内容:

# 子组件通过props来接受数据

# 第一种方法
props: ['childCom']
# 第二种方法
props: {
    childCom: String //这里指定了字符串类型,如果类型不一致会警告的哦
}
# 第三种方法
props: {
    childCom: {
        type: String,
        default: 'sichaoyun' 
    }
}
# (2)子组件向父组件传值

通过绑定事件然后及$emit传值

vue2.0只允许单向数据传递,我们通过出发事件来改变组件的数据

1.父组件parent代码如下:

 <template>
   <div class="parent">
     <h2></h2>
     <p>父组件接手到的内容:</p>
     <son psMsg="我是你爸爸" @transfer="getUser"></son> 
      <!-- 监听子组件触发的transfer事件,然后调用getUser方法 -->
   </div>
 </template>
 <script>
 import son from './Son'
 export default {
   name: 'HelloWorld',
   data () {
     return {
       msg: '父组件',
       username:'',
     }
   },
   components:{son},
   methods:{
     getUser(msg){
       this.username= msg
     }
   }
 }
 </script>

父组件通过绑定自定义事件,接受子组件传递过来的参数

2.子组件son代码如下:

 <template>
   <div class="son">
     <p></p>
     <p>子组件接收到内容:</p>
     <!--<input type="text" v-model="user" @change="setUser">-->
     <button @click="setUser">传值</button>
   </div>
 </template>
 <script>
 export default {
   name: "son",
   data(){
     return {
       sonMsg:'子组件',
       user:'子传父的内容'
     }
   },
   props:['psMsg'],
   methods:{
     setUser:function(){
       this.$emit('transfer',this.user)//触发transfer方法,this.user 为向父组件传递的数据
     }
   }
 }
 </script>

子组件通过$emit触发父组件上的自定义事件,发送参数

# (3)非父子传参(兄弟传参、隔代传参等)

假设你有两个Vue组件需要通信: A 和 B ,A组件按钮上面绑定了点击事件,发送一则消息,B组件接收。

# 1. 初始化,全局创建$bus

直接在项目中的 main.js 初始化 $bus :

// main.js
window.$bus=new Vue();

注意,这种方式初始化一个全局的bus 。

# 2. 发送事件

$bus.$emit("aMsg", '来自A页面的消息');

<!-- A.vue -->
<template>
   <button @click="sendMsg()">-</button>
</template>

<script> 
//import $bus from "../bus.js";
export default {
 methods: {
   sendMsg() {
     $bus.$emit("aMsg", '来自A页面的消息');
   }
 }
}; 
</script>

接下来,我们需要在 B页面 中接收这则消息。

# 3. 接收事件

$bus.$on("事件名",callback)

<!-- IncrementCount.vue -->
<template>
 <p></p>
</template>

<script> 
//import $bus  from "../bus.js";
export default {
 data(){
   return {
     msg: ''
   }
 },
 mounted() {
   $bus.$on("aMsg", (msg) => {
     // A发送来的消息
     this.msg = msg;
   });
 }
};
</script>

# 扩展

# 事件总线推荐下面写法

集中式的事件中间件就是 Bus。我习惯将bus定义到全局:
app.js

var eventBus = {
    install(Vue,options) {
        Vue.prototype.$bus = vue
    }
};
Vue.use(eventBus);

然后在组件中,可以使用$emit, $on, $off 分别来分发、监听、取消监听事件:

# 分发事件的组件
methods: {
  todo: function () {
    this.$bus.$emit('todoSth', params);  //params是传递的参数
  }
}
# 监听的组件
created() {
  this.$bus.$on('todoSth', (params) => {  //获取传递的参数并进行操作
      //todo something
  })
},
// 最好在组件销毁前
// 清除事件监听
beforeDestroy () {
  this.$bus.$off('todoSth');
}

如果需要监听多个组件,只需要更改 bus 的 eventName:

created() {
  this.$bus.$on('firstTodo', this.firstTodo);
  this.$bus.$on('secondTodo', this.secondTodo);
},
// 清除事件监听
beforeDestroy () {
  this.$bus.$off('firstTodo', this.firstTodo);
  this.$bus.$off('secondTodo', this.secondTodo);
}

# 理论

1.父传子: 在父组件的子组件标签上绑定一个属性,挂载要传输的变量。在子组件中通过props来接受数据,props可以是数组也可以是对象,接受的数据可以直接使用 Bus.$off(“事件名”)

2.子传父:

vue2.0只允许单向数据传递,我们通过出发事件来改变组件的数据
在父组件的子组件标签上通过绑定自定义事件,接受子组件传递过来的事件。子组件通过$emit触发父组件上的自定义事件,发送参数(第一个是要改变的属性值,第二个是要发送的参数)

3.兄弟组件传值:

通过main.js初始化一个全局的$bus,在发送事件的一方通过$bus.$emit(“事件名”,传递的参数信息)发送,在接收事件的一方通过$bus.$on("事件名",参数)接收传递的事件
Bus.$on(“事件名”)

# 2 后端

# 2.1 代码计时器(用来优化代码执行速度)

TimeInterval timer = DateUtil.timer();

//---------------------------------
//-------这是执行过程
//---------------------------------

Console.log( timer.interval());//花费毫秒数
Console.log( timer.intervalRestart());//返回花费时间,并重置开始时间
Console.log( timer.intervalMinute());//花费分钟数

# 2.2 拦截器和过滤器

# 2.2.1 拦截器和过滤器的区别

1、过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。

2、拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理的,spring的功能可以被拦截器使用,在拦截器里注入一个service,可以调用业务逻辑。而过滤器是JavaEE标准,只需依赖servlet api ,不需要依赖spring。

3、过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射

4、Filter是依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用。

5、Filter的执行由Servlet容器回调完成,而拦截器通常通过动态代理(反射)的方式来执行。

6、Filter的生命周期由Servlet容器管理,而拦截器则可以通过IoC容器来管理,因此可以通过注入等方式来获取其他Bean的实例,因此使用会更方便。 在这里插入图片描述 最简单明了的区别: 1.过滤器可以修改request,而拦截器不能 2.过滤器需要在servlet容器中实现,拦截器可以适用于javaEE,javaSE等各种环境 3.拦截器可以调用IOC容器中的各种依赖,而过滤器不能 4.过滤器只能在请求的前后使用,而拦截器可以详细到每个方法 调用方法流程如下: 在这里插入图片描述

# 2.2.2 过滤器

# 1、 实现方式

1.使用spring boot提供的FilterRegistrationBean注册Filter 定义Filter:

public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("MyFilter");
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}
12345678910111213141516

注册Filter:

@Slf4j
@Order(1)
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("-----------------------MyFilter");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
123456789101112131415161718

2.使用原生servlet注解定义Filter

@WebFilter(filterName = "LoginFilter" ,urlPatterns = "/*")
@Slf4j
@Order(2)
public class LoginFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        log.info("进入过滤器init");
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("进入过滤器"+servletRequest.getRemoteAddr()+"|"+servletRequest.getRemoteHost()+"|"+servletRequest.getLocalPort()+"|"+servletRequest.getServerPort()
        );
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {
        log.info("进入过滤器destroy");
        Filter.super.destroy();
    }
12345678910111213141516171819202122

这里直接用@WebFilter就可以进行配置,同样,可以设置url匹配模式,过滤器名称等。这里需要注意一点的是@WebFilter这个注解是Servlet3.0的规范,并不是Spring boot提供的。除了这个注解以外,我们还需在启动类中加另外一个注解:@ServletComponetScan,指定扫描的包。

# 2、应用场景

1)过滤敏感词汇(防止sql注入) 2)设置字符编码 3)URL级别的权限访问控制 4)压缩响应信息

# 2.2.2 拦截器

# 1、实现方式

1.自定义拦截器

@Slf4j
public class AuthInterceptor implements HandlerInterceptor {
    @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        log.info("preHandle");
        String clientIP = ServletUtil.getClientIP(httpServletRequest);
        log.info("访问IP:"+clientIP);
        log.info("请求路径:{}", httpServletRequest.getRequestURI());
        return true;
    }

    @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        log.info("postHandle");

    }

    @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
        log.info("afterCompletion");
    }
123456789101112131415161718

2.注册拦截器

@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {

    private final AuthInterceptor authInterceptor;

    public WebMvcConfig(AuthInterceptor authInterceptor) {
        this.authInterceptor = authInterceptor;
    }

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/**");
    }
}
123456789101112131415

# 2.2.3 应用场景

1.登录验证,判断用户是否登录。 2.权限验证,判断用户是否有权限访问资源,如校验token 3.日志记录,记录请求操作日志(用户ip,访问时间等),以便统计请求访问量。 4.处理cookie、本地化、国际化、主题等。 5.性能监控,监控请求处理时长等。 6.通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现)

# 2.3 Redis 经验之谈

redis是性能比较好的nosql,这里总结下个人使用的经验总结:

  • 【key命名】

1、全局规划,key值前缀作为命名空间 : key - value第一级的key值前缀要全局规划好,避免冲突导致互相覆盖。 2、key不要包含空格符,命名不要过长 : 含有空格容易导致输入错误,命名过长浪费内存空间。 3、key前缀不要出现魔鬼数字,防止硬编码 : key值前缀应该要清晰明了,容易理解。

  • 【内存】

1、避免内存泄露 : redis是内存数据库,随着数据的越来越多,占用会越来越大,要注意垃圾回收,定期清理垃圾数据。 2、合理规划内存占用,养成节约内存的习惯: 1)收到系统最大内存限制,一般应用实例缓存控制在最大内存的2/3以内。 2)大数据内存占用应该接近实际存储容量,但还要实测分析内存占用是否合理 3)预留一定的内存空间,一般redis只适用机器内存1/2到2/3,内存其实分为两部分: - 缓存数据占用的内存,对应于redis.conf配置的maxmemory。 - 处理高并发请求分配的临时内存,并发请求越多,临时内存越多。 3、key的数量越少越好 : 减少key数量可以降低内存开销 4、权衡value的取值为,尽量使用纯数字可以节省内存:比如把“true”改为1,“false”改为 0。

  • 【事务】

1、优先使用批量接口获取批量数据: redis的交互越多越耗时,另外也不能保证其原子性,使用redis批量接口命令,其次才使用事务。 2、集合操作为主的业务考虑使用set数据结构:set有丰富的去交集、差集等命令,简单高效,保证了事务的原子性。 3、redis只支持简单事务,redis事务是一个隔离的操作,事务中的所有命令都会序列化,按顺序执行,事务的执行过程中,不会被其他客户端发过来的命令请求所打断。 4、redis对事务的支持没有其他关系型数据库那么强大,redis在事务失败时不会进行回滚,而是继续执行余下命令,它不保证原子性(所有指令同时成功或同时失败)。只有决定开始执行全部指令的能力,没有执行到一半回滚的管理。如果事务执行到一半redis被kill掉,已经执行的指令同样不会被回滚。

  • 【性能】

1、尽量减少和redis的交互次数:交互的越多越耗时,另外原子性也得不到保证,尽量使用批量命令。 2、调优参数 : 通过调整set_max_inset_entries参数,从def的512调整到1024。但是占用内存会增大。 3、redis最佳使用方式是cache非持久化:其他模式会占用更多的资源,需要根据具体项目做决定。

  • 【可靠性】

1、需要定时监控redis的健康情况:使用各种redis健康监控工具,实在不行可以定时返回redis 的 info信息。 2、客户端连接尽量使用连接池(长链接和自动重连)

  • 【安全性】

1、无用户管理和权限管理: 全局只有一个超户,对应一个密码。 2、不安全的客户端访问方式。

redis不适合作为海量数据存储方案,redis适合在数据规模较小,性能要求较高的条件下使用。

# 3 前后端工作安排

# 一、前后端分离的基本概念

前端后端交互,基本上是基于http+json的形式。后端专注于提供数据,更重要职责是维护系统架构的稳定,保证数据的安全。前端人员专注于交互,快速响应UI的变化。

双方交互基于http+json接口,后端人员基本只对接口负责,无需负责js和html的代码。前端人员只对界面展示交互负责,对于后端http接口如何提供正确的数据无需负责。

前后端分离的主要概念就是:后台只需提供API接口,前端调用AJAX实现数据呈现。

# 二:前后端分离的意义

1:彻底解放前端,前端不再需要向后台提供模板或是后台在前端html中嵌入后台代码

2:提高工作效率,分工更加明确,前后端分离的工作流程可以使前端只关注前端的事,后台只关心后台的活,两者开发可以同时进行,在后台还没有时间提供接口的时候,前端可以先将数据写死或者调用本地的json文件即可,页面的增加和路由的修改也不必再去麻烦后台,开发更加灵活。

3:局部性能提升,通过前端路由的配置,我们可以实现页面的按需加载,无需一开始加载首页便加载网站的所有的资源,服务器也不再需要解析前端页面,在页面交互及用户体验上有所提升。

4:降低维护成本,通过目前主流的前端MVC框架,我们可以非常快速的定位及发现问题的所在,客户端的问题不再需要后台人员参与及调试,代码重构及可维护性增强。

5、有利于产品的组件化,由于前后端分离,有利于迅速二次开发推出新产品。

6、减少后端新人上手项目的难度,提高产品的可维护性和可拓展性。

7、基于原有后端接口,有利于后期在安卓,ios,微信等其他不同平台进行产品二次开发。

# 三:实现分离的基本合作思路

1、评审阶段:产品经理与前后端进行需求评审,各自理解清楚自己的业务量以及联调的工作量,评估开发时间。

2、开发准备阶段:前后端一起商量需求中需要联调的部分,进行接口的口头协议交流。

3、接口定义阶段:前后端一方中,前后端中的一方根据之前的口头协议拟定出一份详细的接口,并编写服务接口定义,完成后由另一方确认。有疑问的地方重新商量直至双方都没有问题。

4、开发阶段:双方根据协商出来的接口为基础进行开发,如在开发过程中发现需要新增或删除一些字段,重复步骤3。

(注意:前端在开发过程中记得跟进接口,mock数据进行本地测试,后端修改接口需要跟前端协商清楚再改。 )

5、联调阶段:双方独自的工作完成,开始前后端联调,如在联调过程发现有疑问,重复步骤3,直至联调完成。

6、提测阶段:将完成的需求提给测试人员,让其对该需求进行测试,如发现问题,及时通知开发并让其修改,直至需求没有bug。

7、发布阶段:前后端双方在保证步骤1-5都没有问题了,进行各自的代码发布,完成后由测试人员在线上进行相应的测试,如果有bug,重复步骤6和7,直至成功上线。

# 四:相关问题及解决建议

1、前后端分离会带来前后端沟通成本的问题,相比不分离,能减少开发的总时间吗?

能减少开发的总时间,理由如下:

(1)、基于对接口负责的原则,前后端分离后,只需做好各种熟悉领域的事情。

后端专注于提供数据,更重要职责是维护系统架构的稳定,保证数据的安全。

前端人员专注于交互,快速响应UI的变化。

(2)、前后端分离确实会带来沟通成本的问题,这方面需要前后端遵守合作流程,适应新的合作模式,可以提高沟通效率。总体而言,利大于弊。

2、接口定义阶段,接口谁定?

回答:建议后端开发人员定,需要前端人员评审。

3、联调阶段,前端是基于后端的开发人员的机器联调,还是基于后端一个开发公共环境联调?

回答:前端应该基于后端的一个公共开发环境联调,理由如下:

(1)、开发过程中,后端开发人员机器环境不稳定,后端人员在调速中会时不时进行断点调试,重启机器的服务器。

(2)、公共开发环境由开发人员负责更新程序,并需要在更新程序前把代码提交代码仓库,这样有利于前端有一个实时更新,稳定的调试环境。

# 前后端分离的核心:后台提供数据,前端负责显示