使用 run
命令直接创建一个简单的 deployment
:
kubectl run nginx-deployment –image=nginx
需要控制台会输出一些提示:
1 | kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead. |
提示, run
deployment
的方式未来会被舍弃掉.
查看 pods:
第一个即为刚创建的 pod. 第二个是之前测试 yml 方式创建的 pod(只是一个 pod, 具体参照 nginx-pod.yml 文件)
查看 deployments
:
kubectl get deployment -o wide
查看 svc
:
kubectl get svc
目前还只有一个之前 kubernetes
这一个.
查看 rs
:
kubectl get rs
replicas
的方式。1 | kubectl scale --replicas=2 deployment/nginx-deployment |
查验 rs
和 pods
:
查看 deployments
, 也变为2个了 (kubectl get deployments
)
注意: 这时删除 pod
时,直接删除 pod 会触发 replicas 的确保机制, 从而导致删除 pod 失败. 正确做法是直接删除 pod 对应的 deployment.
1 | kubectl expose deployment nginx-deployment --port=30001 --target-port=80 |
kubectl get pod -o wide
输出
1 | NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES |
Cluster-ip 是集群内部分配的地址,通过 curl 10.100.28.184:30001 即可访问.
此时, 还不能在集群外访问内部的服务.
编辑 deployment
的类型:
kubectl edit svc nginx-deployment
tpye
默认是 ClusterIP.
保存后, 可以看到控制台输出: service/nginx-deployment edited
注:保存后,在无语法错误的前提下,该文件会被修改,如,里面的 nodePort: 30260, 即为集群对外暴露的访问端口,30001 是集群内访问端口号.
1 | # 查看 svc: |
当然, pod/deployment 也可以 edit.
kubectl edit deployment nginx-deployment
kubectl edit pod nginx-pod
测试访问:
Run
命令,通常是通过命令行方式去创建。kubectl run nginx-deployment –image=nginx:1.7.9 –replicas=2
Apply
方式kubectl apply -f nginx.yaml
Nginx.yml
文件内容:
1 | apiVersion: extensions/v1beta1 #api的版本 |
kubectl apply
不但能够创建 Kubernetes 资源,也能对资源进行更新。
Kubernets 还提供了几个类似的命令,例如 kubectl create
、kubectl replace
、kubectl edit
和 kubectl patch
简单直观快捷,上手快。
适合临时测试或实验
基于配置文件的方式:
配置文件描述了 具体是什么,即应用最终要达到的状态。
配置文件提供了创建资源的模板,能够重复部署。
可以像管理代码一样管理部署。
适合正式的、跨环境的、规模化部署。
这种方式要求熟悉配置文件的语法,有一定难度.
核心相关知识点
Kubernetes集群(Cluster)也采用了典型的“主-从”架构。一个集群主要由管理组件(Master)和工作节点(Node)组件构成.
Master组件提供所有与管理相关的操作,例如调度、监控、支持对资源的操作等。
Master会通过Node Controller来定期检查所管理的Node资源的健康状况,完成Node的生命周期管理.
Node是实际工作的计算实例(在1.1之前版本中名字叫做Minion)。节点可以是虚拟机或者物理机器,在创建 Kubernetes 集群过程中,都要预装一些必要的软件来响应Master的管理。
Node节点有几个重要的属性:
地址信息
、阶段状态
、资源容量
、节点信息
默认情况下, 集群对外暴露的端口范围为 30000 ~ 32767 之间.
以 Nginx
为例, 创建 nginx-pod.yaml 文件:
1 | apiVersion: v1 # 描述文件所遵循KubernetesAPI的版本 |
在 master/cluster
节点上执行:
1 | kubectl apply -f /path/to/nginx-pod/yaml |
查看:
kubectl get pods -o wide
如何在集群外访问该服务?
方式1(端口转发):
kubectl port-forward –address 0.0.0.0 nginx-pod 9999:80
访问 http://192.168.56.104:9999/
很容易发现, 通过端口转发的方式只适合本地测试使用,一旦 port-forward
的进程终止后, 服务就无法访问了.
当然通过 nohup
等类似方式实现进程后台运行也可以, 但是终究不够优雅, 这也不是官方推荐的, prod
环境下, 更不推荐如此的使用!
Q:创建 pod
A:命令:kubectl apply -f nginx-pod.yaml
Q:创建 service
A:命令:kubectl apply -f nginx-nodePort.yaml 发布该 service
假设 service的 nginx-nodePort.yam文件如下
1 | apiVersion: v1 |
Q:删除 service
A:假设 service 创建时使用的是 nginx-nodePort.yaml,则删除时,直接:
kubectl delete -f nginx-nodePort.yaml
Q:卸载 kubernets
A:kubeadm reset
手动安装, 需要解决各种证书、依赖等等的问题
一种实现自我管理的方式,包括 k8s 本身自己。
Kubeadm 工具,k8s 官方提供的集群部署管理工具。
K8s 的组件均以容器方式运行。
Master 结点上的 4 个组件:
1 | scheduler |
上述 master 四个组件也运行在 POD 上(静态 POD。
kubeasz
工具
https://github.com/easzlab/kubeasz
部署规模化生产环境的需求,推荐使用 kops
或者 SaltStack
这样更复杂的部署工具(根据张磊老师推荐)
借助 kubeadm 这一工具快速完成搭建与学习.
主要是: kubeadm, kubelet, kubectl (所有机器上都需要)
创建 repo
源:
1 | [kubernetes] |
安装:
yum install -y kubelet kubeadm kubectl –disableexcludes=kubernetes
启动初始化工具并设定随机启动:
systemctl enable kubelet && systemctl start kubelet
解除防火墙限制:
1 | vi /etc/sysctl.d/k8s.conf |
预下载/拉取镜像(Master 机器执行):
kubeadm config images list # 查看集群初始化所需镜像及依赖版本号
一般会失败,需要手动拉取,从阿里云拉取。使用脚本统一一次性拉取:
1 | mkdir scripts |
脚本具体内容: https://gitee.com/lomospace/k8s/blob/master/scripts/k8s_base_image.sh
执行脚本:
1 | cd scripts |
通过 kubeadm config images pull
确认镜像拉取成功:
1 | kubeadm init --kubernetes-version=v1.16.2 --apiserver-advertise-address=192.168.56.104 --pod-network-cidr=192.168.0.0/16 |
报错了:
解决方案: 修改 虚拟机 CPU,重启后重新执行kubeadm init
操作即可.
init 常用主要参数:
Init 过程日志输出:
默认情况下, 生成的 token 有效期为 24h
继续:
1 | mkdir -p $HOME/.kube |
该步骤必须执行, 否则会提示: The connection to the server localhost:8080 was refused - did you specify the right host or port?
查看 pods
状态
kubectl get pods -n kube-system
在上面 init
最后, 输出了形如: kubeadm join 192.168.56.104:6443 --token ........
, 复制,然后在 node 机器上执行即可.
查看所有 nodes
:
kubectl get nodes
此时发现均为 NotReady
状态, 因为 master
节点还未安装网络插件.
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
部署完成后, 过一会即可看到它自动刷新后的结果:
注意: 集群中的master和node节点的hostname不能重复,否则会加入集群失败!!!
删除集群中的 node
:
kubectl delete node node3
重新创建 token:
kubeadm token create
查看 token:
Kubeadm token list
查找 hash:
]]>openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed ‘s/^.* //‘
所需软件: VirtualBox(测试所用物理机为 MBP 顶配)
本地虚拟机集群, 所有机器均借助 VirtualBox 和 CentOS7 实现.
虚拟主机系统统一采用:CentOS-7-x86_64-Minimal-1804.iso
iso 下载地址: http://mirrors.aliyun.com/centos/7/isos/x86_64/CentOS-7-x86_64-Minimal-1804.iso
hostname
为 k8s-master
, k8s-node01
, k8s-node02
设置 hostname 使用 hostnamectl 命令即可
如: hostnamectl set-hostname k8s-node01
虚拟机启动后, 窗口调整快捷键为: command + c
虚拟机安装使用可参考: https://blog.csdn.net/yang_yun_hao/article/details/87917657
1> virtualbox 设置
偏好设置 -> 网络 -> 添加新 NAT 网络, 双击并勾选启用网络、支持 DHCP
1 | sudo vim /etc/sysconfig/network-scripts/ifcfg-enp0s3 |
默认情况下, 虚拟机的 IP
由 VirtualBox的 DHCP
服务动态分配, 这在多态虚拟机的情况下不好管理, 最好一台机器固定一个 IP
, 这时就需要配置静态 IP
.
管理 -> 主机网络管理器,点击创建按钮即可。
然后每个虚拟机配置上该网卡即可。
ip addr|grep inet
配置这个, 是为了方便直接在物理机登陆虚拟机系统, 方便操作.
方式1:
使用 ssh-copy-id
命令将 key 写到远程机器的 ~/.ssh/authorized_key 文件中.
(该方法必须2端操作时为同一个用户名)
方式2:
将 本地机器的 rsa copy 至所需登陆的服务机器上.
scp ~/.ssh/id_rsa.pub root@192.168.56.101:.ssh/id_rsa.pub
在B上的命令 (先于 A执行,A - 为本地机器):
touch /root/.ssh/authorized_keys # (如果已经存在这个文件, 跳过这条)
将id_rsa.pub文件从 A本地机器上 上传到 B 机器后再执行该操作:
cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys # (将id_rsa.pub的内容追加到authorized_keys 中)
测试登陆:
ssh root@k8s-master
ok.
添加 alias 更方便登陆:
1 | vim ~/.bash_profile |
每台机器都安装
1 | yum install -y epel-release |
2.1 关闭防火墙
1 | systemctl stop firewalld && systemctl disable firewalld |
2.2 关闭 selinux
1 | setenforce 0 && sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config |
2.3 关闭 swap
单纯的执行 swapoff
只是临时关闭, 下次重启后还是会打开. 因此需要修改文件:
vim /etc/fstab # 将 swap 分区一行注释即可.
使用命令行关闭方式:
1 | swapoff -a && sed -i "s/\/dev\/mapper\/centos-swap/\#\/dev\/mapper\/centos-swap/g" /etc/fstab |
wget -O /etc/yum.repos.d/docker-ce http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
安装 docker(所有机器执行):
yum install docker-ce -y
但是提示没有可用软件包(-_-||):
解决方案1:
1 | # 依次执行如下命令: |
解决方案2:
1 | sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo |
启动 docker 并设定随机启动: systemctl enable docker && systemctl start docker
调整 docker
部分参数
1 | mkdir -p /etc/docker |
1 | { |
修改前:
确认 docker 的 Cgroup Driver 信息:
docker info |grep Cgroup
默认是 Cgroup Driver: cgroupfs
修改后:
Cgroup Driver: systemd
重启 docker:
1 | # mkdir -p /etc/systemd/system/docker.service.d |
主要是针对容器内应用网络访问问题的一次小记.
由于种种缘由,使用了一个开源的 BI 服务 ( Metabase )。起初,该服务在测试环境部署时毅然选择了当下主流技术:Docker。测试环境部署以及使用了一段时间后,产生了一些数据,这些数据也需要随之迁移至线上环境。该服务写了一个数据在宿主机的磁盘上,以 .db
文件的形式存在。
将测试环境的容器镜像 commit
出来一个新的镜像,然后将该镜像部署至线上机器的容器中。
1 | sudo docker ps -a |
然后,将新 commit
出来的镜像 .tar
包文件弄出来,什么 scp
, ftp
之类的。
当然使用 docker save
后保存的文件再被 download 时可能提示权限问题,使用 chmod
命令解决即可。
推荐使用 docker save/load, 使用 docker export/import 可能有一些问题,二期导出来的包内容也不一样,可自行比较。
机器:xxxx
线上域名:xxx.xx.com
将上面的镜像搞到线上机器后,使用 docker load
命令将该镜像加载.
Nginx 配置:
1 | upstream ${server_name} { |
然后,创建容器,启动。
1 | sudo docker run -d -p 3000:3000 -v ~/metabase-data:/metabase-data -e "MB_DB_FILE=/metabase-data/metabase.db" --name 容器名 镜像名 |
sudo docker ps -a
可以看到启动的容器,但是该服务链接数据库时,就是链接不上 !!!
进入容器,sudo docker exec -it xxx /bin/bash
, ping
数据库发现无法连通。但是在测试环境该方式是正常的。
怀疑环境隔离问题,新搞一个 mysql
容器服务来测试一下.
mysql
镜像直接使用阿里的即可,参考:https://www.cnblogs.com/loovelj/p/7823093.html
启动该容器,进入容器内使用 mysql
链接时,发现还是无法链接。查看官方文档了解到 docker network
: https://docs.docker.com/network/
重新创建一个 mysql
容器服务,并指定 network
, --net host
, 成功链接。
最后,删除原来的容器服务,重新创建容器并指定 network
, 至此问题解决。
业务层接口获取数据 GET
新接口时, 遇到以下错误
1 | AttributeError: Could not locate column in row for column 'iteritems' |
Debug 过程:
首先,在 controller
层接口代码添加 pdb
调试入口.
1 | def get(lotan_session, offset, limit): |
然后,重启服务,模拟请求,程序运行到按照设置的 pdb
处,终端进入另一个类似 python
的 console
, 即开启 pdb
调试.
过程如图:
注意到 lotan_session.build_model(QualityScore, id=score.id)
, 在 build
model 时,使用了 QualityScore
这个数据模型, 且根据 id 这个字段来进行定义
build`, 而该数据模型类定义的方法为:
1 | def resolve_by_id(self): |
DAO 层的 get_by_id 方法:
1 |
|
查看 lotan
的 build_model
方法:
1 | def build_model(self, model_cls, **kwargs): |
前面的 if
只是简短的类型判断. 关键: find_model_by_name
这个方法, 该方法定义:
1 | # ModelManager 的类, 元类 |
__metaclass__ = SingletonMeta
表示创建 ModelManager
这个类对象时会采用 SingletonMeta
类 来创建 ModelManager
类. 故, SingletonMeta
类在此处是 ModelManager
的元类。
__init__
中初始化了 名为_models_map
的 dict
, 从 .get()
方法基本可以确定需要传入的参数是一个 dict
类型.
进而返回查看: return score
, 使用 pdb
查看该变量属性:
1 | (Pdb) type(score) |
类型明显不对. 即问题所在 return
处。将返回改为 dict(score)
即可。
1 | (Pdb) dict(score) |
PDB
具体用法, https://www.ibm.com/developerworks/cn/linux/l-cn-pythondebugger/index.html
Python
元类, http://blog.jobbole.com/21351/
存储过程,类似程序语言的一组/系列 实现特定功能的 SQL
组合(包含了常见的 SQL 语句、基本的流程控制, if else、 case when then、concat 等等).
它经过编译后存储在数据库中, 以后不需要再次编译而可以直接调用(call).
存储过程的格式类似函数, 有名字、参数.
常用的 MySQL
, 其存储过程的定义有三种参数类型: in
, out
, inout
创建:
1 | -- # 关键字 create 和 procedure |
调用:
call sp_xxName()
, 调用时 存储过程名后面必须加 ()
, 无论其定义时是否有参数.
删除:
drop procedure sp_xxName()
查看:
show procedure status
显示数据库中所有存储的存储过程基本信息,包括所属数据库,存储过程名称,创建时间等
show create procedure sp_name
显示某一个mysql存储过程的详细信息
以 MySQL
DBMS 为例.
e.g.1 不带参数的存储过程
创建一个简单(不带参数)的存储过程:
create procedure sp_test() select 1;
查看创建的这个存储过程:
show create procedure sp_test;
调用存储过程:
call sp_test();
e.g.2 带输入参数
输入参数(in关键字),调用存储过程时必须要传入该参数,且存储过程中修改该参数的值不能被返回.
DELIMITER原本就是“;”的意思,因此用这个命令转换一下“;”为“//”,这样只有收到“//”才认为指令结束可以执行.
创建一个带参数的存储过程:
1 | delimiter // |
调用存储过程:
call sp1_test(1); //
e.g.3(带输出参数)
关键字out,表示该参数值 在 存储过程内部 改变并返回该值;
创建存储过程:
1 | create procedure sp2_test(out p int) |
调用:
必须要加 @ 符号!
call sp2_test(@max); //
获取调用后的结果:
select @max; //
e.g.4 (带输入输出参数)
创建:
1 | drop procedure if exists sp3_test // |
调用:
call sp3_test(1, @res1); //
获取调用后的结果:
1 | select @res1; // |
结果不是111,因为表中之前已存的最大数是212。
Call sp3_test(3, @res2); //
获取存储过程返回值:
1 | mysql> select @res2; // |
e.g.5(同时作输入输出参数的存储过程):
drop procedure if exists sp4_test //
创建:
1 | create procedure sp4_test(inout s int) |
调用:
1 | -- #先设定变量: |
RXJS 是 ReactiveX编程理念/思想的 JavaScript实现版本. 其它语言里, 如 Java 有对应的 RxJava[https://github.com/ReactiveX/RxJava]
ReactiveX是一种针对异步数据流的编程; ReactiveX 是由微软xx架构师创造.
ReactiveX 参考: http://reactivex.io/intro.html
Observable
数据生产、传播
Observer
数据消费
Subscriber
连接 Observable 和 Observer
Operator
数据流、传播途中对数据值进行操作/转换的操作符
Subject
包含 Observable 和 Observer
1 | mkdir rxjs_exercise |
e.g. 1 【from】
app.ts
1 | import { Observable, } from 'rxjs'; |
1 | ts-node app |
subscribe
方法里有三个方法参数, from
操作符用来读取 person
对象, subscribe
里的方法开始执行时, Observable
才开始想它推送 (person)对象数据.
e.g. 2 [create]
1 | import {Observable} from 'rxjs/Observable'; |
1 | # ts-node create 运行结果如下: |
Observable.create()
方法接受一个 function
方法参数. 通过运行结果发现, 当订阅者每消费一条数据, observable
就会推送一条数据, 并逐次推送对象里的数据给消费者, 直到 complete()
, 从而形成 流式
.
尝试将 .subscriobe()
方法里的三个方法注释掉,
1 | generateData().subscribe( |
运行结果:
1 | 推送第 1 条数据 |
整个数据流的传递 只有 Observable
在被订阅(.subscribe()
)消费时才会去推送数据流(不管订阅者是否处理消费, 只要订阅了 就给你推送).
常见操作符: of
, map
, reduce
, filter
, take
, first
, Timer
, Interval
.
操作符本质上是一个 function
, 用来处理、加工 Observable
中传递的数据流里的数据. 这个 操作符 function()
输入、输出(返回)均为Observable
类型
e.g.
1 | import { Observable } from 'rxjs/Observable'; |
1 | # ts-node operator |
通过 map 方法获取数据流的所有salary, 并交给reduce 计算获取总和
参考:
https://www.jianshu.com/p/d8cb71554008
https://segmentfault.com/a/1190000008834251
错误处理需要在 数据流 到达 Observer
之前拦截处理。
从Angular变更到React技术栈…
吾队, 统一使用React
了, 别问为什么, 我想了好几天也没搞明白.
写吧写吧.
如今, 就一个人好好搬砖吧.
本次项目直接使用antd pro
, 阿里的那一套……
由于本次直接使用的是
ali
的antd pro 中台解决方案. 基本相当于二次开发……
安装脚手架
1 | npm install dva-cli -g |
本地运行:
1 | cd projectName |
1 | # 未包含隐藏.xx相关配置文件 |
mock
目录下都是mock数据用的文件
public
目录下存放静态资源文件
src
项目的源码
tests
单元测试代码
关于以 .
开头的隐藏文件说明:
.roadhogrc.mock.js
该文件主要是用来在本地mock
数据时 拦截、代理本地接口请求用的. 具体使用, 后续会讲到..webpackrc.js
webpack打包配置相关设定, 可以看到已经将antd
这个库设置为打包进去了, 使用react
官方脚手架,创建时 时需要人为配置..eslint.js
, .babelrc.ks
都是相关配置文件, 暂不需要过度关注和修改.1 | . |
assets
文件夹, 静态资源存放common
文件夹, 公共的, 按照ant pro设定就2个文件components
文件夹, 纯UI组件e2e
, end 2 end 测试models
文件夹, 存放模型Model(和数据操作密切相关的)文件, 和dva
密切关联.utils
, 一些常用函数工具类库services
, 存放一些接口请求相关的封装routes
文件夹, 存放页面相关的route component(不是纯UI组件, 其包含了一些具体的页面操作交互、方法定义、周期钩子函数componentDidMount
等).layouts
, 整个页面的布局设定polyfill.js
, 兼容性设置index.js
, 整个应用入口需求: 需要展示一个 product
列表, 并在左侧导航栏的二级菜单下有一个入口.
src/common/menu.js
文件.path: 'management'
关键字, 在 children
下按照同样的语法格式添加即可. 该语法和 Angular
中的路由配置类似.1 | path: 'management', |
src/common/router.js
文件. 在 routerConfig
对象找个合适的位置按照同样的语法添加即可.1 | '/management/product_line': { |
注意,
dynamicWrapper
方法的第二个数组参数, 通常用来设置该路由组件(即ProductLine)所对应的Model.
View
层的路由组件1 | <!-- 页面视图 --> |
模态框的简单封装:
1 | <!-- 上面👆那个页面用到的模态框封装 --> |
针对上述3中的route component
组件, 发现其中有几个方法以及与接口API
交互的方法.
第一个: 进入当前页面路由后, 初始化加载的数据. 在 ProductLine
类中可以看到:
1 | componentDidMount() { |
这是
React
提供的生命周期钩子函数,render
渲染到DOM
时就会自动触发该钩子函数, 从而发起请求fetch
; 具体写法:model的namespace/方法名
.
在类中调用该接口请求, 另一个必须的依赖就是 dva
(阿里基于redux + react-router + redux-saga 等库的轻量封装). 使用 connect
连接到该组件的 model
该组件的model如下:
1 | import { |
可以看到
view
层的router component
组件调用了其对用的model
里封装的方法. 但是实际的接口地址在哪里呢? 在下面👇
在上面的第4步中可以看到该
model
从services
文件夹下的productline
中导入了几个方法.
接口 service
如下:
1 | import request from '../utils/request'; |
这里配置的URL 都是本地mock的, 默认是被本地mock接口拦截的.
至此, 整个数据流向和API请求使用, 按照上面的步骤 一步步下来 就很清晰了.
Ant Design Pro 内置使用的是
dva
.
对照此图即可 明了.
应用的数据流向(和API交互过程)
Action:
Action是把数据从应用(如:用户输入、点击事件触发的数据、或是从接口请求获取的数据) 传递到store的一个载体。
Action本质上是JS 普通对象。约定:Action内必须有一个字符串类的type 字段,用来表示将要执行的动作。
一般/多数情况下,type会被定义成字符串常量;
Reducer:
指定 应用状态的变化如何响应actions并发送到store(actions只描述有事情发生这一事实,没有描述应用如何更新state);
Reducer是个纯函数,主要作用:接受旧的state和action,返回新的state。Redux应用中, 所有的state都被保存在一个单一对象中。所以,reducer函数中 只是单纯进行计算(返回新的state),没有API请求、没有变量修改、
Store:
Store就是把action和reducer联系到一起的对象。
React 并不是Web应用的完整解决方案, 只是DOM的抽象层.
React中值传递 都是向下传递的、单向的; 只能从父级 流向 子级.
组件声明规范: 无论是函数方式还是ES6中的class方式声明组件, 组件的命名规范: 首字母必须大写!
dva
是alibaba基于 redux
+ react-router
+ redux-saga
的一层轻量封装.
Redux和react没有 什么直接关系.
Redux不仅支持react, 也支持angular、ember、jQuery、纯JS.
Ant Design of React 只是一个React相关的组件库。类似angular相关的UI库 ng-zorro. 而 Ant Design Pro相当于一个更上层的应用/模板,协助快速开发出应用。
dva
使用:练习:
https://gitee.com/lomospace/dva-sample
Google即可.
练习:
https://gitee.com/lomospace/antPro
Doc(中文)
https://cn.redux.js.org/
视频(英文)
https://egghead.io/lessons/
官方:
https://github.com/facebook/create-react-app
dva系:
1 | # install |
https://dvajs.com/guide/getting-started.html#%E5%AE%89%E8%A3%85-dva-cli
]]>combinations
示例: 给定一个数组 [‘a’, ‘b’, ‘c’, ‘d’], 求该数组的所有子串集合
转为求字符串 ‘abcd’ 子串 (答案:a, b, c, d, ab, ac, ad, bc, bd, abc, abd ….. abcd 共15个).
借助 combinations
函数实现随机不重复组合.
1 | arr = ['a', 'b', 'c', 'd'] |
for
循环中 print
输出是 list(tuple) 类型:
1 | [('a',), ('b',), ('c',), ('d',)] |
combinations(str, n), 该方法即为 取 str 中的n个字符组合.
combinations(string, i+1)
的返回值为 combinations object
:
形如:
1 | <itertools.combinations object at 0x10ca179b0> |
所以, 该方法主要是用来组合!!!
permutations
1 | >>> list(permutations([1,3,5], None)) |
继续使用上面的例子.
1 | res_per = [] |
res_per
的输出结果为:
1 | ['a', 'b', 'c', 'd', 'ab', 'ac', 'ad', 'ba', 'bc', 'bd', 'ca', 'cb', 'cd', 'da', 'db', 'dc', 'abc', 'abd', 'acb', 'acd', 'adb', 'adc', 'bac', 'bad', 'bca', 'bcd', 'bda', 'bdc', 'cab', 'cad', 'cba', 'cbd', 'cda', 'cdb', 'dab', 'dac', 'dba', 'dbc', 'dca', 'dcb', 'abcd', 'abdc', 'acbd', 'acdb', 'adbc', 'adcb', 'bacd', 'badc', 'bcad', 'bcda', 'bdac', 'bdca', 'cabd', 'cadb', 'cbad', 'cbda', 'cdab', 'cdba', 'dabc', 'dacb', 'dbac', 'dbca', 'dcab', 'dcba'] |
共 64 个组合.
该方法主要用来排列!!!
参考实现:
1 | def combinations(iterable, r): |
解析 …
python 3.7
https://docs.python.org/3.7/library/itertools.html
python 2.7
https://docs.python.org/2/library/itertools.html
这2个方法都是用来向数组/list 中增加数据的。
.append(x)
接受的参数: 可以是一个数字或字符串,也可以是一个数组 list 或 tuple 或 dict, 简言之,.append()可接受任何类型的数据塞进已有的 list 中.
1 | >>> test3 |
注意:如果 append 传入的参数 x 是 数组 list 类型,则该 list 维数会在原来的基础上+1,如 ori = [], ori.append([1,2,3]), 则 ori = [[1,2,3]] 变为二维数组了,使用 extend 则不会。
.extend(x)
接收的参数必须是一个str类型的字符串或 list 数组类型 或 tuple 类型.
如,e.g.
1 | test2 = [1,3,5] |
可以看到传入 dict
时,extend 只会把 dict 的 key 取出来 append 到数组最后。
排列与组合公式:
]]>前几天被问,只想起来第一种,其它几种忘记了,特此补补。
第一种,通过继承Thread类,重写
run
方法。
1 | <!-- 测试类 --> |
结果:
1 | --子类Main--Lomo1 |
每次运行,可以看到顺序并不同,说明多线程里,那个线程先执行、什么时候执行 均取决于CPU资源的调度。
在注释中m.start();
是无法多次调用该方法实现多线程共享同一个对象资源的,因为:start()
方法是启动/创建一个新线程,新线程会执行相应的run方法,其不能被同一个对象重复调用。
通过实现Runnable接口重写其
run()
方法.
1 | /** |
运行结果:
1 | constructor is called --lomoa |
第一种,继承
Thread
类的实现方法,通过查看Thread
的源码,发现其也是实现了Runnable
接口方法,本质上是一样的。
通过实现Runnable
接口实现方式的有点:
1 | 1. 避免Java中类单继承带来的问题。假设:某个类A已继承了类B,此时需要将类A放入多线程,那么通过实现接口的方式去实现是最好的(接口可以实现多继承) |
通过实现
Callable
接口的call()
方法。该call方法作为多线程处理逻辑,类似前面的run方法。call方法要求有返回值.
1 | package javaClassExercise.multiThreading; |
运行结果:
1 | 线程 main 的当前循环变量值为:0 |
昨天被问到Java中接口、抽象类、类的区别/差异。突然懵逼了,只是简单回答了成员属性以及方法声明的简单差异(太尴尬了,其实这个问题很简单~~)
反思下: 因为确实很久没有写
Java
代码了(大学又非CS专业,第一份工作中基本没有用到过Java
),Java知识还是来到现东家后靠自己业余时间现学现用。
Notes For 2018-05-26(PM) and for study !
先上代码(show me the code, no bb😁)
e.g.
1 | <!-- 接口 actionList --> |
1 | <!-- 抽象类 --> |
1 | <!-- 普通类 --> |
Update
针对第⑥点总结示例:
1 | /** |
在ide中调试直接运行, 可以发现可以被执行了。输出
1 | 包含main方法的抽象类 |
即 调用了抽象类的main方法。
对其进一步改进:
1 | public class abstractStaticMethod extends staticAbstractClass{ |
右键运行abstractStaticMethod
方法,输出结果
1 | Lomo 168 aa aa... |
可以看到运行该public
时,其并没有调用父类抽象类的main
方法,而是只执行本类的main
方法。(此外,构造函数执行顺序:父类(抽象类)->子类(本类) ).
通常,抽象类
中定义main
方法好像意义不大!
① 关于成员属性(变量)方面:
接口中的成员属性一般为
static final
修饰,即:默认访问权限为public且接口中声明的成员属性一般为写死的(final)不能为修改.
② 关于static关键字方面:
接口中不能包含static修饰的方法或static静态代码块. 可以手动尝试,在接口中声明一个static 代码块或方法,IDE就会直接报错!😁
③ 关于方法的声明方面:
接口中的所有方法均无方法体(即无具体的方法实现逻辑、运算过程…),抽象类中一般都包含抽象方法(即无具体方法体的方法,只声明了函数名以及函数访问修饰符、返回值类型、参数个数、参数类型),但是抽象类中可以包含有具体实现的方法也可以包含静态代码块(接口则不行)。抽象类的抽象方法修饰符一般为
public
或protected
(无private, 如果是private则无法被继承的类去继承重写该方法!)且抽象方法也没有具体的方法体实现,只有声明,与接口中的类似.
④ 关于继承方面:
一个普通类一次只能继承
extends
一个类(该类可以是普通类、抽象类),但是可以同时实现implements
多个接口,继承抽象类时,需要在该类中实现抽象类中的所有抽象方法,实现几个接口就要重写实现接口中的所有(抽象)方法.
另, 一个接口可以同时继承多个其它接口
。 但是,一个接口不能实现另一个接口!!!
1 | // 单一接口继承多个接口 |
⑤ 关于构造器方面:
接口无构造器; 抽象类、类可以有(手动显式/默认隐式)
⑥ main方法方面:
接口中不能有
main
主函数方法,而抽象类可以有(见上例)、普通类可以有.
⑦ 添加新方法方面:
接口中添加新方法,需要考虑那些实现了该接口的类(必须要改变、操作实现了该接口的类),而添加在抽象类中,则可以给出默认具体实现而不必去修改该类的子类.
另,Java是单继承!!!
]]>一个流行的容器编排引擎、自动化容器操作的开源平台。
主要是针对集群,包含但不限于:对容器的部署、调度、节点集群扩展等等功能。
迷你版minikube(来自阿里云)。方便本地部署学习Kubernetes(k8s)。
以Mac OSX上实验为例。
1> 安装Kubectl
Kubectl安装直接使用brew
即可。
2> 安装VM(Virtual Box虚拟机)
官网下载.dmg
文件双击安装即可。
3> minikube
1 | # 执行以下命令即可 |
4> 启动
minikube默认使用virtual box启动本地 Kubernetes 环境(利用本地虚拟机部署 Kubernetes环境)。所以前面要求安装VM,否则执行到该步骤会报错.
1 | # terminal exec |
此时,会看到控制台下载了一些镜像文件。
下载完后:
5> 启动web UI(Dashboard)
1 | minikube dashboard |
执行后,会自动打开浏览器跳至URL http://192.168.99.100:30000/#!/overview?namespace=default
如图:
通过控制台console
发现,minikube
前端是使用Angular
开发(1.6.6版本,😁)
后续继续研究 k8s
… …
简单记录Observable. 该设计模式是RxJS的核心之一。
一个目标对象 管理者所有依赖于它的观察者对象,当它(该目标对象)发生改变时,它会主动发出通知,告知所有订阅了该目标对象的观察者对象,进而使这些观察者对象自我更新。
示例:
很多媒体、新闻网站都会有订阅功能,网友通过订阅该网站的某些栏目或类别的信息,就会定期收到更新的内容通知,网友就可以进行阅览。当网友取消订阅,就不会再接收任何消息。
在该过程中:
该网站 => 目标对象;网友 => 观察者;
这是一个简单的一对多模型。
RxJS
常见的Web应用中对DOM添加事件监听( addEventListener
)。
1 | // 目标对象类,被订阅者 |
]]>ng2+中; 后续添加项目应用code.
平时写 SQL 用(到)过的一些 MySQL 内置函数.
e.g.1
version
表:
1 | +-------------+---------------------+------+-----+---------------------+----------------+ |
project_id
只有2个值 分别为 1、2 代表 iOS
和 Android
.
Q: 从version表取出: 指定版本号(version)、阶段(stage)的最小build号对应的id
A: 查出结果集
1 | select * from version where version='5.26.0' and stage='grey' order by build limit 1; |
1 | +----+------------+---------+-------+-------+---------------------+---------------------+------------------------------------------+ |
只获取所需的 id
1 | select v.id from (select * from version where version='5.26.0' and stage='grey' order by build limit 1) as v; |
1 | +----+ |
使用了 select
子查询. 注意子查询语句后的 as
语法.
join 默认为 left join
e.g.2
现同一DB下有另一张 table
名为 mr
, 结构如下:
1 | mysql> desc mr; |
mr
表中的 version_id
字段和 version
表中的 id
相等.
Q: 给定一个版本号(如: 5.26.0
)、且 stage=grey
情况下 获取 mr
表中该版本该 stage
对应的数据.
1 | select mr_id, jira_key, title, author,pa_name,changed_file,additions,deletions |
子查询中使用了 limit 1
表示 只取 version
表中的最小的 id
.
e.g.3
Q: 查询 iOS
所有版本的 mr
关键信息
A:
1 | select distinct version.version as '版本号',sum(mr.changed_file) as '修改文件数', |
1 | +-----------+-----------------+--------------------+--------------------+-----------------+ |
e.g.4
Q: 以pa_name 为维度 查询某个Android grey阶段 某个版本 (如: version='5.26.0' , stage='grey' , project_id=2
) mr 表中的关键信息.
A:
1 | select count(mr_id) as 'MR 数',pa_name as 'PA',sum(changed_file) as '修改文件数', |
结果集:
1 | +--------+-----------------+-----------------+--------------------+--------------------+ |
现有另一张表 diff
, 其中某些字段类型 和 mr
表的一些字段(pa_name, changed_file, additions, deletions)含义类似. 结构如下:
1 | mysql> desc diff; |
Q: 现需要获取, Android 某个版本(如: 5.26.0) 灰度(stage=grey) 所有 mr
与 diff
表中的 changed_file, additions, deletions
总和 结果以 pa_name
升序排列.
A:
1 | select t.pa_name as 'PA', sum(t.changed_file) as '修改文件数', |
1 | +-----------------+-----------------+--------------------+--------------------+ |
涉及到 union all
组合查询.
union
会把重复的记录从结果集中去除掉;unoin
会把所有的记录返回, 所以效率比上一个高.
现有 downtime
表, 结构如下:
1 | mysql> desc downtime; |
e.g.1
Q: 以 level
为维度统计本年度(2018) downtime
表中的故障信息
A:
1 | select count(*) as '数量', level as '等级', year(created_at) as '创建时间' |
1 | +--------+--------+--------------+ |
使用了 内置的 year
函数. year
函数根据 表中存储的时间戳(如: ‘2018-08-28 14:58:00’), year(2018-08-28 14:58:00)
返回 2018
.
e.g.2
Q: 求本年度downtime平均时长
使用 TIMESTAMPDIFF
来计算两个时间段之间的差值.
这个mysql中的函数 可以用来计算2个时间段差值。第一个参数是精确单位: YEAR/ MONTH/QUARTER/WEEK/DAY/HOUR/MINUTE/SECOND/FRAC_SECOND
FRAC_SECOND: 毫秒
A:
1 | select avg(TIMESTAMPDIFF(HOUR, start_time, end_time)) from downtime where year(created_at)=2018; |
e.g.3
Q: 以周为维度统计本年度 downtime 信息(P2 +)
A:
1 | select concat('第', week(created_at,1), '周') as 'Week', count(*) as '新增 Downtime 数量', |
使用 week函数,第二个参数 1 代表 从每周从 Monday开始计算(mysql默认无参数表示从 sunday周日 算起 为一周开始).
1 | +----------+------------------------+----------------------------------+ |
e.g.4
Q: 以月为维度统计本年度 downtime 信息(P2 +)
A:
1 | select concat('第', month(created_at), '月') as 'Month', count(*) as '新增 Downtime 数量', |
使用 month
函数, 该函数默认返回的是当前时间对应的月份(数字 1~12)
monthname函数
函数可以获得对应的英文格式的月份.
A:
1 | select monthname(created_at) as 'Month', count(*) as '新增 Downtime 数量', |
结果集:
1 | +-----------+------------------------+----------------------------------+ |
e.g.5
Q: 以季度为维度统计本年度 downtime 信息(P2 +)
A:
1 | select concat('Q-', quarter(start_time)) as '季度', count(start_time) as '新增 Downtime 数量', |
结果集:
1 | +--------+------------------------+----------------------------------+ |
季度统计,主要使用 quarter
函数.
MySQL时间日期函数参考: http://wiki.jikexueyuan.com/project/mysql/useful-functions/time-functions.html
]]>记录📝点Angular项目开发过程中 不熟悉的、陌生的、自认为有需要注意的。
以 autoPublisher
Project为例.
项目结构划分:
1 | #root |
核心功能主要在app/js
下实现。 controllers
主要是对整个控制器。directives
是对页面每个块儿的定义以及监听数据变化然后调用Service
方法进行页面渲染、数据更新等。filters
是对接口返回的数据进行二次处理与包装的过滤器。services
文件夹下的Service主要是对公共方法封装以及提供给directive中使用的一些方法。
在页面中这 controllers
directives
filters
services
的引用顺序:
1 |
|
首先,控制器index.controller
依赖:
1 | // 依赖api.service.js的jdbAutoPublisherAPIModule ... 将其依赖一次添加至jdbAutoPublisherApps模块。 |
在controller
中如何正确使用Filter
?
首先,依赖注入$filter
, 其次,使用语法:
$filter('filter名字')(参数)
即可。
再看看 indexFishBone.directive
, 其需要调用fishBone.service
和 api.service
以及 fishBoneAction.serivce
里的方法,那么只需在controller的moudle声明是添加对应的依赖,然后再这个directive中的function里注入依赖的Service名即可。
BTW,如果在directive的return里声明了scope: {}
则表示该directive的scope
作用域与controller中以及该应用其它地方声明的所有scope
作用域都是隔离的。
其次,directive中如何获取controller中的值?或:controller与directive如何通信?
当controller通过Service或filter获取到数据后,绑定到scope
作用域,在directive中使用$watch
监听该作用域上的值即可。(前提:directive中的Scope作用域未隔离!)
1 | // wathch第一个参数为绑定在scope作用域上的变量 |
由setTimeout
引发的一些了解 -> 事件循环机制、异步队列、时钟周期、执行上下文
js里常见的2个定时器:
setTimeout
和setInterval
,指定一定时间后触发其第一个参数函数(异步回调函数)然后执行函数体内的代码发生一些操作、变化….
e.g.1
1 | setTimeout(function(){console.log(4)},0); |
Pormise,
Promise
对象代表一个异步操作,它有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和 Rejected(已失败),Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。
先看
1 | setTimeout(function () { |
在浏览器控制台可以看到好像确实是立马就输出123了。
先看下
1 | for (var i = 0; i < 1000; i++) { |
这个很好理解,常规思维都是顺序执行,这个结果也是如常规思维所想。
再看(改变下顺序):
1 | setTimeout(function () { |
在浏览器控制台执行,发现先输出的是for
循环内的内容,无论循环条件里是1000还是100 或是 10,都是先执行的循环体最后执行的setTimeout
分析:首先代码被JS解释引擎(如V8)顺序加载解释并执行,到遇到setTimeout
函数时,会将其第一个function参数函数放入异步队列排队等待线程空闲后再按照队列顺序执行。
new Promise
是第一个参数是一个同步函数,new Promise
最终返回的结果是一个promise
对象, .then()
方法也是异步的,所以代码执行到此处时会将.then()
的回调函数放入一个异步队列,当new Promise
返回的promise
达到fulfilled状态时且当前线程空闲时就会执行.then()
内的异步回调函数输出结果5, 再接着执行console.log(3);
控制台输出3。
最后当前JS线程已将代码遍历完并执行完,然后获取异步队列的内回调函数,这里关键在于:为什么先执行的是new Promise
返回的Promise
对象的.then()
里的方法而不是setTimeout
里的参数函数。因为浏览器或webview的时间钟,时间钟是由机器硬件的时间周期决定(CPU时钟周期)。
总结:js主线程在执行当前代码这个线程手里,当前线程只有空闲后,才回去处理事件队列,虽然setTimeout
设定的时间已到,但是也得等队列里所有代码已执行完毕,最后才处理setTimeout
里的东西。
1 | setTimeout("console.log('test!')", 1000); |
执行试试。必须要等待用户点击确认按钮后才会继续执行。
因为alert挂起了主线程,使得当前主线程被block(同样的函数还是prompt()
, confirm()
),主线程被挂起,整个当前执行js的进程进入等待状态,事件触发或代码执行都被中断,计时器也会暂停计时 ,当主线程恢复后,余下的代码继续执行,计时器安装时钟周期重新开始计时。
再看外国网站的例子[JakeArchibald.com]
e.g.2
1 | console.log('script start'); |
运行输出:
1 | script start |
Promise
对象经过resolve
后的.then
是异步的. 所以promise1 和 promise2的输出会在第3、4行输出;(setTimtout是因为 异步队列+系统时钟周期导致最后被执行–即当前线程空闲时)
进一步修改示例
e.g.3
1 | console.log('script start'); |
运行后输出:
1 | script start |
再修改:
e.g.4
1 | console.log('script start'); |
1 | #输出: |
这是因为
new Promise()
里接受一个函数参数function(resolve,reject)
用来执行异步执行成功或失败后的处理逻辑。
1 | let promise = new Promise(function(resolve, reject){ |
new Promise()
里的参数函数,在代码被执行到此处时可以认为是同步执行的,其返回的Promise对象使用.then链式调用时其内的函数是被异步执行。
参考:
https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
https://github.com/abbshr/abbshr.github.io/issues/32
http://www.jianshu.com/p/063f7e490e9a [Promise基础]
]]>指令directive的控制器controller、链接link和编译compile
directive
指令:
1 | <div class="row"> |
directive.js
1 | detailsApp.directive('jdbIntroductionhtml', function(utilsService) { |
在chrome Console
看到输出顺序:3.directive -- compile
, 1.directive -- controller
,发现link并未打印。是link function没有执行吗。
了解下它们之间的区别:
angular对directive指令执行顺序:编译调用compile生成dom对象,再调用link绑定到对应的$scope域(设置事件监听器,监视数据变化和实时的操作DOM.),最后执行controller;
使用场景:
控制器和link函数可以进行互换;控制器主要是用来提供可在指令间复用的行为,但链接函数只能在当前内部指令中定义行为,无法被其它指令间复用;link函数可以将指令互相隔离开来,而controller则定义可复用的行为以供其它指令来调用该指令;
link
对特定元素添加/注册事件、增加功能、使用scope的$watch()或者想要与DOM元素做实时的交互;
简言之,在directive的controller中写的方法操作等可以被暴露出来给其他指令使用;link函数中写的方法只在本指令中使用、有效;compile指令在Angularjs解析我们自定义的HTML标签时最先执行的编译,将自定义的标签进行编译然后进行link或controller 最后产出供浏览器可以识别的DOM进行页面渲染….
参考:
]]>本文主要内容:
Mac 下 基于 Appium
的自动化测试环境配置笔记。(所有环境版本号以当前最新版本为基础)
1 | # 系统环境 |
其它依赖的开发软件:
Xcode、Android studio,Android Studio 安装完毕后,下载 SDK ( Android )。
1 | # Java,推荐使用 JDK8 或 JDK11,暂不推荐最新版 JDK12 |
Java 及其相关环境变量配置参考:
1 | JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/" |
Appium 安装有 2 种方式:
1.GUI 版
下载 dmg 文件安装即可.
该种方式对应的 WDA 文件路径:
/Applications/Appium.app/Contents/Resources/app/node_modules/appium/node_modules/appium-xcuitest-driver/WebDriverAgent
2.无 GUI 版
npm install -g appium
安装指定版本:
npm install -g appium@1.13.0
此种方式的 WDA 路径:
/usr/local/lib/node_modules/appium/node_modules/_appium-webdriveragent@1.2.0@appium-webdriveragent
如果 npm
较慢或者有其它问题,则使用 cnpm 即可.
npm install -g cnpm –registry=https://registry.npm.taobao.org
1.Carthage
套件管理工具,与 CocoaPods 类似。
详细说明可参考: https://gist.github.com/weihanglo/97e949a9dbf92deb111999b6e42e9654
brew update && brew install carthage
2.ios-deploy
终端安装和调试iPhone应用的是第三方开源库
使用参考: https://www.jianshu.com/p/9b9136fa1444
安装通过 npm
npm install -g ios-deploy
3.libimobiledevice
该开源工具包,支持 Linux 链接 iPhone 等 iOS 设备.
这个工具包的作用就是要做一个类似于 Android 中的 adb 命令行工具!!!
brew update && brew install libimobiledevice –HEAD
查看 iOS 设备 uuid:
idevice_id –list
4.ideviceinstaller
方便查看 iOS 设备上安装的 APP 的 bundleId
brew install ideviceinstaller
使用:
ideviceinstaller –help
ideviceinstaller -l # 列出本机安装的所有 app 的bundle id
安装 ipa:
ideviceinstaller -i xxx.ipa
卸载 ipa
ideviceinstaller -U [bundleID]
如果使用时报错类似如下信息:
1 | Could not connect to lockdownd. Exiting. |
解决方案:
1 | brew uninstall -f libimobiledevice ideviceinstaller usbmuxd |
参考: https://github.com/libimobiledevice/ideviceinstaller
4.xcpretty
主要是增加 xcodebuild 输出可读性. 可选 !
gem install xcpretty
Gem 是 Ruby 依赖包管理工具。类似于 iOS 开发中的 CocoaPods,Java 中的 maven、ant …..
配置好 JAVA、Android 相关的环境变量,安装一个 SDK,并选择同意相关的 License,然后就直接配置参数链接真机操作即可。
本地编译 WDA 主要是为了 iOS 解决相关证书问题。该步骤针对链接 iOS 设备是必经步骤!
桌面版/无 GUI 版,按照上述的安装路径,打开对应的 xcode 工程, 打开项目导航(点击最左侧导航栏文件夹按钮),然后右侧会展示 PROJECT 导航,选择 TARGETS 下的,分别选中 WebDriverAgentLib 和 WebDriverAgentRunner,修改其 Signing
签名,改为自己的 Apple ID 即可,同时修改 Build Settings 下 Packaging 下的 Bundle Identifier。
主要是将原来的 xxx.facebook.xx.wda.xx 中的 facebook 改一下即可,为了区分 bundleId
选择手机编译至对应的 iOS 设备上即可 (AgentRunner)。
注意: 如果 Build 真机还报错,则将下面的几个 Targets 页添加上, 主要是 bunlderId 修改和 Signing.
添加成功后,会看到签名对应处 Signing Certificate iPhone Developer: 你的 Apple ID (Team ID: 一个长度为10的大写字母串)
Desired Capabilities 配置如下:
1 | { |
设备名称通过 idevicename
即可获取
uuid 通过 idevice_id -l
获取通过 USB 链接的 iOS 设备列表
1 | { |
关注点:
1 | 1. 可扩展性 |
具体框架设计细节暂不赘述.
]]>需求:用户登录时,如果用户名或密码输入框输入错误,予以相应的错误提示并将错误提示实现类似于jQuery
的 fadeOut()
效果,几秒后自动消失。
使用 ng-show
或 ng-if
来显示对应错误,这2个ng指令属性默认值为false
。
当controller
work起来触发了相应的条件时,controller会将对应的 ng-show
或 ng-if
值置为 true
, 并给对应的错误信息变量赋值.
html部分
1 | <p class="error-info"> |
#双大括号在某些条件不友好,可使用
ng-cloak
等
参考:
http://lomo.space/2016/12/21/angular-study-note/#Angular表达式
js部分
1 | var loginAPP = angular.module("loginApp", ['loginModel']); |
当触发条件后,JS手动修改的model {{isError}}
的值,model被修改后并未通知AngulaJS, 所以ng 不会主动去update view, 需要手动调用 $scope.$apply();
.
$scope.$apply()
这种无参形式调用,不推荐!应该将对应的函数作为一个参数传给$apply()
,Angular的 $apply()
会将传入的参数function包装进 try catch
块儿中,当参数函数抛异常时就能被catch块儿捕获。
修改后:
1 | $timeout(function() { |
总结:当使用原生JS或其它非AngularJS内置函数/方法更新了Model后都需要手动调用 $apply()
以此来通知Angular的 watcher
, watcher 被触发后,Angular就会检测scope模型model。