首页 文章 精选 留言 我的

精选列表

搜索[快速入门],共10000篇文章
优秀的个人博客,低调大师

基于Docker Compose快速创建Web测试环境

背景介绍 在Web UI自动化测试的时候,往往我们需要进行分布式的测试,或者是并发测试来加快自动化测试的效率。这时候,我们就需要大量的浏览器实例。早期的做法是,在一台比较好的服务器上利用虚拟机来启动多个操作系统实例,每个操作系统中装有浏览器。这个缺点是启动实例的速度慢,同时服务器的资源消耗严重,很难在同一台服务器上开启非常多的虚拟机实例。因此,我们将Docker引入进来,利用Docker轻量级的特性,来达到秒级的浏览器容器实例创建速度。 Docker 技术实现 我们以一个简单的Robot Framework自动化测试过程,来演示如何创建chrome与firefox实例。 第一步,编写Compose文件 首先,我们编写docker-compose文件,如下图所示。 Compose file 第二步,启动selenium hub 与 node-chrome, node-firefox实例 > sudo docker-compose up -d Containers 第三步,执行测试脚本 > robot BasicDemo.robot BasicDemo.robot 查看测试结果 robot 测试报告 测试用例运行截图 以上样例,存放在github的wywincl/SeleniumTest项目中,可以自己克隆下来学习实验。 小彩蛋。 fortune + cowsay + lolcat 参考文档 Docker+Selenium Grid构建分布式Web测试环境

优秀的个人博客,低调大师

快速用Discuz搭建论坛网站教程

Discuz! 是全球成熟度最高、覆盖率最大的论坛软件系统之一。自 2001 年 6 月面世以来,Discuz! 已拥有 15 年以上的应用历史和 200 多万网站用户案例。目前,Discuz! 已经发展成为一个以社区为基础的专业建站平台,让论坛(BBS)、社交网络(SNS)、门户(Portal)、群组(Group)、开放平台(Open Platform)应用充分融合于一体,帮助网站实现一站式服务。 适用对象 适用于熟悉 ECS,熟悉 Linux 系统,刚开始使用阿里云进行建站的用户。 基本流程 使用云服务器 ECS 搭建 Discuz 论坛网站的操作步骤如下: 安装 Discuz 相关镜像。 验证 Discuz 镜像。 创建数据库。 配置域名(非必需步骤)。 安装配置 Discuz。 步骤一 安装 Discuz 相关镜像 1.通过阿里云云市场免费获取

优秀的个人博客,低调大师

16个核心概念带你入门 Kubernetes

Kubernetes是Google开源的容器集群管理系统,是Google多年⼤规模容器管理技术Borg的开源版本,主要功能包括: 基于容器的应用部署、维护和滚动升级 负载均衡和服务发现 跨机器和跨地区的集群调度 自动伸缩 无状态服务和有状态服务 广泛的Volume支持 插件机制保证扩展性 Kubernetes发展非常迅速,已经成为容器编排领域的领导者,接下来我们将讲解Kubernetes中涉及到的一些主要概念。 1、Pod Pod是一组紧密关联的容器集合,支持多个容器在一个Pod中共享网络和文件系统,可以通过进程间通信和文件共享这种简单高效的方式完成服务,是Kubernetes调度的基本单位。Pod的设计理念是每个Pod都有一个唯一的IP。 Pod具有如下特征: 包含多个共享IPC、Network和UTC namespace的容器,可直接通过localhost通信 所有Pod内容器都可以访问共享的Volume,可以访问共享数据 优雅终止:Pod删除的时候先给其内的进程发送SIGTERM,等待一段时间(grace period)后才强制停止依然还在运行的进程 特权容器(通过SecurityContext配置)具有改变系统配置的权限(在网络插件中大量应用) 支持三种重启策略(restartPolicy),分别是:Always、OnFailure、Never 支持三种镜像拉取策略(imagePullPolicy),分别是:Always、Never、IfNotPresent 资源限制,Kubernetes通过CGroup限制容器的CPU以及内存等资源,可以设置request以及limit值 健康检查,提供两种健康检查探针,分别是livenessProbe和redinessProbe,前者用于探测容器是否存活,如果探测失败,则根据重启策略进行重启操作,后者用于检查容器状态是否正常,如果检查容器状态不正常,则请求不会到达该Pod Init container在所有容器运行之前执行,常用来初始化配置 容器生命周期钩子函数,用于监听容器生命周期的特定事件,并在事件发生时执行已注册的回调函数,支持两种钩子函数:postStart和preStop,前者是在容器启动后执行,后者是在容器停止前执行 2、Namespace Namespace(命名空间)是对一组资源和对象的抽象集合,比如可以用来将系统内部的对象划分为不同的项目组或者用户组。常见的pod、service、replicaSet和deployment等都是属于某一个namespace的(默认是default),而node, persistentVolumes等则不属于任何namespace。 常用namespace操作: kubectlgetnamespace, 查询所有namespace kubectl createnamespacens-name,创建namespace kubectldeletenamespacens-name, 删除namespace 删除命名空间时,需注意以下几点: 删除一个namespace会自动删除所有属于该namespace的资源。 default 和 kube-system 命名空间不可删除。 PersistentVolumes是不属于任何namespace的,但PersistentVolumeClaim是属于某个特定namespace的。 Events是否属于namespace取决于产生events的对象。 3、Node Node是Pod真正运行的主机,可以是物理机也可以是虚拟机。Node本质上不是Kubernetes来创建的, Kubernetes只是管理Node上的资源。为了管理Pod,每个Node节点上至少需要运行container runtime(Docker)、kubelet和kube-proxy服务。 常用node操作: kubectlgetnodes,查询所有node kubectl cordon $nodename, 将node标志为不可调度 kubectl uncordon $nodename, 将node标志为可调度 taint(污点) 使用kubectl taint命令可以给某个Node节点设置污点,Node被设置上污点之后就和Pod之间存在了一种相斥的关系,可以让Node拒绝Pod的调度执行,甚至将Node已经存在的Pod驱逐出去。每个污点的组成:key=value:effect,当前taint effect支持如下三个选项: NoSchedule:表示k8s将不会将Pod调度到具有该污点的Node上 PreferNoSchedule:表示k8s将尽量避免将Pod调度到具有该污点的Node上 NoExecute:表示k8s将不会将Pod调度到具有该污点的Node上,同时会将Node上已经存在的Pod驱逐出去 常用命令如下: kubectl taint node node0 key1=value1:NoShedule,为node0设置不可调度污点 kubectl taint node node0 key-,将node0上key值为key1的污点移除 kubectl taint node node1 node-role.kubernetes.io/master=:NoSchedule,为kube-master节点设置不可调度污点 kubectl taint node node1 node-role.kubernetes.io/master=PreferNoSchedule,为kube-master节点设置尽量不可调度污点 容忍(Tolerations) 设置了污点的Node将根据taint的effect:NoSchedule、PreferNoSchedule、NoExecute和Pod之间产生互斥的关系,Pod将在一定程度上不会被调度到Node上。但我们可以在Pod上设置容忍(Toleration),意思是设置了容忍的Pod将可以容忍污点的存在,可以被调度到存在污点的Node上。 4、Service Service是对一组提供相同功能的Pods的抽象,并为他们提供一个统一的入口,借助 Service 应用可以方便的实现服务发现与负载均衡,并实现应用的零宕机升级。Service通过标签(label)来选取后端Pod,一般配合ReplicaSet或者Deployment来保证后端容器的正常运行。 service 有如下四种类型,默认是ClusterIP: ClusterIP: 默认类型,自动分配一个仅集群内部可以访问的虚拟IP NodePort: 在ClusterIP基础上为Service在每台机器上绑定一个端口,这样就可以通过NodeIP:NodePort来访问该服务 LoadBalancer: 在NodePort的基础上,借助cloud provider创建一个外部的负载均衡器,并将请求转发到 NodeIP:NodePort ExternalName: 将服务通过DNS CNAME记录方式转发到指定的域名 另外,也可以将已有的服务以Service的形式加入到Kubernetes集群中来,只需要在创建 Service 的时候不指定Label selector,而是在Service创建好后手动为其添加endpoint。 5、Volume 存储卷 默认情况下容器的数据是非持久化的,容器消亡以后数据也会跟着丢失,所以Docker提供了Volume机制以便将数据持久化存储。Kubernetes提供了更强大的Volume机制和插件,解决了容器数据持久化以及容器间共享数据的问题。 Kubernetes存储卷的生命周期与Pod绑定 容器挂掉后Kubelet再次重启容器时,Volume的数据依然还在 Pod删除时,Volume才会清理。数据是否丢失取决于具体的Volume类型,比如emptyDir的数据会丢失,而PV的数据则不会丢 目前Kubernetes主要支持以下Volume类型: emptyDir:Pod存在,emptyDir就会存在,容器挂掉不会引起emptyDir目录下的数据丢失,但是pod被删除或者迁移,emptyDir也会被删除 hostPath:hostPath允许挂载Node上的文件系统到Pod里面去 NFS(Network File System):网络文件系统,Kubernetes中通过简单地配置就可以挂载NFS到Pod中,而NFS中的数据是可以永久保存的,同时NFS支持同时写操作。 glusterfs:同NFS一样是一种网络文件系统,Kubernetes可以将glusterfs挂载到Pod中,并进行永久保存 cephfs:一种分布式网络文件系统,可以挂载到Pod中,并进行永久保存 subpath:Pod的多个容器使用同一个Volume时,会经常用到 secret:密钥管理,可以将敏感信息进行加密之后保存并挂载到Pod中 persistentVolumeClaim:用于将持久化存储(PersistentVolume)挂载到Pod中 ... 6、PersistentVolume(PV) 持久化存储卷 PersistentVolume(PV)是集群之中的一块网络存储。跟 Node 一样,也是集群的资源。PersistentVolume (PV)和PersistentVolumeClaim (PVC)提供了方便的持久化卷: PV提供网络存储资源,而PVC请求存储资源并将其挂载到Pod中。 PV的访问模式(accessModes)有三种: ReadWriteOnce(RWO):是最基本的方式,可读可写,但只支持被单个Pod挂载。 ReadOnlyMany(ROX):可以以只读的方式被多个Pod挂载。 ReadWriteMany(RWX):这种存储可以以读写的方式被多个Pod共享。 不是每一种存储都支持这三种方式,像共享方式,目前支持的还比较少,比较常用的是 NFS。在PVC绑定PV时通常根据两个条件来绑定,一个是存储的大小,另一个就是 访问模式。 PV的回收策略(persistentVolumeReclaimPolicy)也有三种 Retain,不清理保留Volume(需要手动清理) Recycle,删除数据,即 rm -rf /thevolume/* (只有NFS和HostPath支持) Delete,删除存储资源 7、Deployment 无状态应用 一般情况下我们不需要手动创建Pod实例,而是采用更高一层的抽象或定义来管理Pod,针对无状态类型的应用,Kubernetes使用Deloyment的Controller对象与之对应。其典型的应用场景包括: 定义Deployment来创建Pod和ReplicaSet 滚动升级和回滚应用 扩容和缩容 暂停和继续Deployment 常用的操作命令如下: kubectl run www--image=10.0.0.183:5000/hanker/www:0.0.1--port=8080生成一个Deployment对象 kubectlgetdeployment--all-namespaces查找Deployment kubectl describe deployment www查看某个Deployment kubectl edit deployment www编辑Deployment定义 kubectldeletedeployment www删除某Deployment kubectl scale deployment/www--replicas=2扩缩容操作,即修改Deployment下的Pod实例个数 kubectlsetimage deployment/nginx-deployment nginx=nginx:1.9.1更新镜像 kubectl rollout undo deployment/nginx-deployment回滚操作 kubectl rollout status deployment/nginx-deployment查看回滚进度 kubectl autoscale deployment nginx-deployment--min=10--max=15--cpu-percent=80启用水平伸缩(HPA - horizontal pod autoscaling),设置最小、最大实例数量以及目标cpu使用率 kubectl rollout pause deployment/nginx-deployment暂停更新Deployment kubectl rollout resume deploy nginx恢复更新Deployment 更新策略 .spec.strategy指新的Pod替换旧的Pod的策略,有以下两种类型 RollingUpdate 滚动升级,可以保证应用在升级期间,对外正常提供服务。 Recreate 重建策略,在创建出新的Pod之前会先杀掉所有已存在的Pod。 Deployment和ReplicaSet两者之间的关系 使用Deployment来创建ReplicaSet。ReplicaSet在后台创建pod,检查启动状态,看它是成功还是失败。 当执行更新操作时,会创建一个新的ReplicaSet,Deployment会按照控制的速率将pod从旧的ReplicaSet移 动到新的ReplicaSet中 8、StatefulSet 有状态应用 Deployments和ReplicaSets是为无状态服务设计的,那么StatefulSet则是为了有状态服务而设计,其应用场景包括: 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现 稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现 有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次进行操作(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现 有序收缩,有序删除(即从N-1到0) 支持两种更新策略: OnDelete:当 .spec.template更新时,并不立即删除旧的Pod,而是等待用户手动删除这些旧Pod后自动创建新Pod。这是默认的更新策略,兼容v1.6版本的行为 RollingUpdate:当 .spec.template 更新时,自动删除旧的Pod并创建新Pod替换。在更新时这些Pod是按逆序的方式进行,依次删除、创建并等待Pod变成Ready状态才进行下一个Pod的更新。 9、DaemonSet 守护进程集 DaemonSet保证在特定或所有Node节点上都运行一个Pod实例,常用来部署一些集群的日志采集、监控或者其他系统管理应用。典型的应用包括: 日志收集,比如fluentd,logstash等 系统监控,比如Prometheus Node Exporter,collectd等 系统程序,比如kube-proxy, kube-dns, glusterd, ceph,ingress-controller等 指定Node节点 DaemonSet会忽略Node的unschedulable状态,有两种方式来指定Pod只运行在指定的Node节点上: nodeSelector:只调度到匹配指定label的Node上 nodeAffinity:功能更丰富的Node选择器,比如支持集合操作 podAffinity:调度到满足条件的Pod所在的Node上 目前支持两种策略 OnDelete: 默认策略,更新模板后,只有手动删除了旧的Pod后才会创建新的Pod RollingUpdate: 更新DaemonSet模版后,自动删除旧的Pod并创建新的Pod 10、Ingress Kubernetes中的负载均衡我们主要用到了以下两种机制: Service:使用Service提供集群内部的负载均衡,Kube-proxy负责将service请求负载均衡到后端的Pod中 Ingress Controller:使用Ingress提供集群外部的负载均衡 Service和Pod的IP仅可在集群内部访问。集群外部的请求需要通过负载均衡转发到service所在节点暴露的端口上,然后再由kube-proxy通过边缘路由器将其转发到相关的Pod,Ingress可以给service提供集群外部访问的URL、负载均衡、HTTP路由等,为了配置这些Ingress规则,集群管理员需要部署一个Ingress Controller,它监听Ingress和service的变化,并根据规则配置负载均衡并提供访问入口。 常用的ingress controller: nginx traefik Kong Openresty 11、Job & CronJob 任务和定时任务 Job负责批量处理短暂的一次性任务 (short lived>CronJob即定时任务,就类似于Linux系统的crontab,在指定的时间周期运行指定的任务。 12、HPA(Horizontal Pod Autoscaling) 水平伸缩 Horizontal Pod Autoscaling可以根据CPU、内存使用率或应用自定义metrics自动扩展Pod数量 (支持replication controller、deployment和replica set)。 控制管理器默认每隔30s查询metrics的资源使用情况(可以通过 --horizontal-pod-autoscaler-sync-period 修改) 支持三种metrics类型 预定义metrics(比如Pod的CPU)以利用率的方式计算 自定义的Pod metrics,以原始值(raw value)的方式计算 自定义的object metrics 支持两种metrics查询方式:Heapster和自定义的REST API 支持多metrics 可以通过如下命令创建HPA:kubectl autoscale deployment php-apache--cpu-percent=50--min=1--max=10 13、Service Account Service account是为了方便Pod里面的进程调用Kubernetes API或其他外部服务而设计的 授权 Service Account为服务提供了一种方便的认证机制,但它不关心授权的问题。可以配合RBAC(Role Based Access Control)来为Service Account鉴权,通过定义Role、RoleBinding、ClusterRole、ClusterRoleBinding来对sa进行授权。 14、Secret 密钥 Sercert-密钥解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者Pod Spec中。Secret可以以Volume或者环境变量的方式使用。有如下三种类型: Service Account:用来访问Kubernetes API,由Kubernetes自动创建,并且会自动挂载到Pod的 /run/secrets/kubernetes.io/serviceaccount 目录中; Opaque:base64编码格式的Secret,用来存储密码、密钥等; kubernetes.io/dockerconfigjson: 用来存储私有docker registry的认证信息。 15、ConfigMap 配置中心 ConfigMap用于保存配置数据的键值对,可以用来保存单个属性,也可以用来保存配置文件。ConfigMap跟secret很类似,但它可以更方便地处理不包含敏感信息的字符串。ConfigMap可以通过三种方式在Pod中使用,三种分别方式为:设置环境变量、设置容器命令行参数以及在Volume中直接挂载文件或目录。 可以使用 kubectl create configmap从文件、目录或者key-value字符串创建等创建 ConfigMap。也可以通过 kubectl create-f value.yaml 创建。 16、Resource Quotas 资源配额 资源配额(Resource Quotas)是用来限制用户资源用量的一种机制。 资源配额有如下类型: 计算资源,包括cpu和memory cpu, limits.cpu, requests.cpu memory, limits.memory, requests.memory 存储资源,包括存储资源的总量以及指定storage class的总量 requests.storage:存储资源总量,如500Gi persistentvolumeclaims:pvc的个数 storageclass.storage.k8s.io/requests.storage storageclass.storage.k8s.io/persistentvolumeclaims 对象数,即可创建的对象的个数 pods, replicationcontrollers, configmaps, secrets resourcequotas, persistentvolumeclaims services, services.loadbalancers, services.nodeports 它的工作原理为: 资源配额应用在Namespace上,并且每个Namespace最多只能有一个 ResourceQuota 对象 开启计算资源配额后,创建容器时必须配置计算资源请求或限制(也可以 用LimitRange设置默认值),用户超额后禁止创建新的资源

优秀的个人博客,低调大师

Python Type Hints 从入门到实践

Python 想必大家都已经很熟悉了,甚至关于它有用或者无用的论点大家可能也已经看腻了。但是无论如何,它作为一个将加入高考科目的语言还是有它独到之处的,今天我们就再展开聊聊 Python。 Python 是一门动态强类型语言 《流畅的 Python》一书中提到,如果一门语言很少隐式转换类型,说明它是强类型语言,例如 Java、C++ 和 Python 就是强类型语言。 同时如果一门语言经常隐式转换类型,说明它是弱类型语言,PHP、JavaScript 和 Perl 是弱类型语言。 当然上面这种简单的示例对比,并不能确切的说 Python 是一门强类型语言,因为 Java 同样支持 integer 和 string 相加操作,且 Java 是强类型语言。因此《流畅的 Python》一书中还有关于静态类型和动态类型的定义:在编译时检查类型的语言是静态类型语言,在运行时检查类型的语言是动态类型语言。静态语言需要声明类型(有些现代语言使用类型推导避免部分类型声明)。 综上所述,关于 Python 是动态强类型语言是比较显而易见没什么争议的。 Type Hints 初探 Python 在 PEP 484(Python Enhancement Proposals,Python 增强建议书)[https://www.python.org/dev/peps/pep-0484/]中提出了 Type Hints(类型注解)。进一步强化了 Python 是一门强类型语言的特性,它在 Python3.5 中第一次被引入。使用 Type Hints 可以让我们编写出带有类型的 Python 代码,看起来更加符合强类型语言风格。 这里定义了两个 greeting 函数: 普通的写法如下: name = "world" def greeting(name): return "Hello " + name greeting(name) 加入了 Type Hints 的写法如下: name: str = "world" def greeting(name: str) -> str: return "Hello " + name greeting(name) 以 PyCharm 为例,在编写代码的过程中 IDE 会根据函数的类型标注,对传递给函数的参数进行类型检查。如果发现实参类型与函数的形参类型标注不符就会有如下提示: 常见数据结构的 Type Hints 写法 上面通过一个 greeting 函数展示了 Type Hints 的用法,接下来我们就 Python 常见数据结构的 Type Hints 写法进行更加深入的学习。 默认参数 Python 函数支持默认参数,以下是默认参数的 Type Hints 写法,只需要将类型写到变量和默认参数之间即可。 def greeting(name: str = "world") -> str: return "Hello " + name greeting() 自定义类型 对于自定义类型,Type Hints 同样能够很好的支持。它的写法跟 Python 内置类型并无区别。 class Student(object): def __init__(self, name, age): self.name = name self.age = age def student_to_string(s: Student) -> str: return f"student name: {s.name}, age: {s.age}." student_to_string(Student("Tim", 18)) 当类型标注为自定义类型时,IDE 也能够对类型进行检查。 容器类型 当我们要给内置容器类型添加类型标注时,由于类型注解运算符 [] 在 Python 中代表切片操作,因此会引发语法错误。所以不能直接使用内置容器类型当作注解,需要从 typing 模块中导入对应的容器类型注解(通常为内置类型的首字母大写形式)。 from typing import List, Tuple, Dict l: List[int] = [1, 2, 3] t: Tuple[str, ...] = ("a", "b") d: Dict[str, int] = { "a": 1, "b": 2, } 不过 PEP 585[https://www.python.org/dev/peps/pep-0585/]的出现解决了这个问题,我们可以直接使用 Python 的内置类型,而不会出现语法错误。 l: list[int] = [1, 2, 3] t: tuple[str, ...] = ("a", "b") d: dict[str, int] = { "a": 1, "b": 2, } 类型别名 有些复杂的嵌套类型写起来很长,如果出现重复,就会很痛苦,代码也会不够整洁。 config: list[tuple[str, int], dict[str, str]] = [ ("127.0.0.1", 8080), { "MYSQL_DB": "db", "MYSQL_USER": "user", "MYSQL_PASS": "pass", "MYSQL_HOST": "127.0.0.1", "MYSQL_PORT": "3306", }, ] def start_server(config: list[tuple[str, int], dict[str, str]]) -> None: ... start_server(config) 此时可以通过给类型起别名的方式来解决,类似变量命名。 Config = list[tuple[str, int], dict[str, str]] config: Config = [ ("127.0.0.1", 8080), { "MYSQL_DB": "db", "MYSQL_USER": "user", "MYSQL_PASS": "pass", "MYSQL_HOST": "127.0.0.1", "MYSQL_PORT": "3306", }, ] def start_server(config: Config) -> None: ... start_server(config) 这样代码看起来就舒服多了。 可变参数 Python 函数一个非常灵活的地方就是支持可变参数,Type Hints 同样支持可变参数的类型标注。 def foo(*args: str, **kwargs: int) -> None: ... foo("a", "b", 1, x=2, y="c") IDE 仍能够检查出来。 泛型 使用动态语言少不了泛型的支持,Type Hints 针对泛型也提供了多种解决方案。 TypeVar 使用 TypeVar 可以接收任意类型。 from typing import TypeVar T = TypeVar("T") def foo(*args: T, **kwargs: T) -> None: ... foo("a", "b", 1, x=2, y="c") Union 如果不想使用泛型,只想使用几种指定的类型,那么可以使用 Union 来做。比如定义 concat 函数只想接收 str 或 bytes 类型。 from typing import Union T = Union[str, bytes] def concat(s1: T, s2: T) -> T: return s1 + s2 concat("hello", "world") concat(b"hello", b"world") concat("hello", b"world") concat(b"hello", "world") IDE 的检查提示如下图: TypeVar 和 Union 区别 TypeVar 不只可以接收泛型,它也可以像 Union 一样使用,只需要在实例化时将想要指定的类型范围当作参数依次传进来来即可。跟 Union 不同的是,使用 TypeVar 声明的函数,多参数类型必须相同,而 Union 不做限制。 from typing import TypeVar T = TypeVar("T", str, bytes) def concat(s1: T, s2: T) -> T: return s1 + s2 concat("hello", "world") concat(b"hello", b"world") concat("hello", b"world") 以下是使用 TypeVar 做限定类型时的 IDE 提示: Optional Type Hints 提供了 Optional 来作为 Union[X, None] 的简写形式,表示被标注的参数要么为 X 类型,要么为 None,Optional[X] 等价于 Union[X, None]。 from typing import Optional, Union # None => type(None) def foo(arg: Union[int, None] = None) -> None: ... def foo(arg: Optional[int] = None) -> None: ... Any Any 是一种特殊的类型,可以代表所有类型。未指定返回值与参数类型的函数,都隐式地默认使用 Any,所以以下两个 greeting 函数写法等价: from typing import Any def greeting(name): return "Hello " + name def greeting(name: Any) -> Any: return "Hello " + name 当我们既想使用 Type Hints 来实现静态类型的写法,也不想失去动态语言特有的灵活性时,即可使用 Any。 Any 类型值赋给更精确的类型时,不执行类型检查,如下代码 IDE 并不会有错误提示: from typing import Any a: Any = None a = [] # 动态语言特性 a = 2 s: str = '' s = a # Any 类型值赋给更精确的类型 可调用对象(函数、类等) Python 中的任何可调用类型都可以使用 Callable 进行标注。如下代码标注中 Callable[[int], str],[int] 表示可调用类型的参数列表,str 表示返回值。 from typing import Callable def int_to_str(i: int) -> str: return str(i) def f(fn: Callable[[int], str], i: int) -> str: return fn(i) f(int_to_str, 2) 自引用 当我们需要定义树型结构时,往往需要自引用。当执行到 init 方法时 Tree 类型还没有生成,所以不能像使用 str 这种内置类型一样直接进行标注,需要采用字符串形式“Tree”来对未生成的对象进行引用。 class Tree(object): def __init__(self, left: "Tree" = None, right: "Tree" = None): self.left = left self.right = right tree1 = Tree(Tree(), Tree()) IDE 同样能够对自引用类型进行检查。 此形式不仅能够用于自引用,前置引用同样适用。 鸭子类型 Python 一个显著的特点是其对鸭子类型的大量应用,Type Hints 提供了 Protocol 来对鸭子类型进行支持。定义类时只需要继承 Protocol 就可以声明一个接口类型,当遇到接口类型的注解时,只要接收到的对象实现了接口类型的所有方法,即可通过类型注解的检查,IDE 便不会报错。这里的 Stream 无需显式继承 Interface 类,只需要实现了 close 方法即可。 from typing import Protocol class Interface(Protocol): def close(self) -> None: ... # class Stream(Interface): class Stream: def close(self) -> None: ... def close_resource(r: Interface) -> None: r.close() f = open("a.txt") close_resource(f) s: Stream = Stream() close_resource(s) 由于内置的 open 函数返回的文件对象和 Stream 对象都实现了 close 方法,所以能够通过 Type Hints 的检查,而字符串“s”并没有实现 close 方法,所以 IDE 会提示类型错误。 Type Hints 的其他写法 实际上 Type Hints 不只有一种写法,Python 为了兼容不同人的喜好和老代码的迁移还实现了另外两种写法。 使用注释编写 来看一个 tornado 框架的例子(tornado/web.py)。适用于在已有的项目上做修改,代码已经写好了,后期需要增加类型标注。 使用单独文件编写(.pyi) 可以在源代码相同的目录下新建一个与 .py 同名的 .pyi 文件,IDE 同样能够自动做类型检查。这么做的优点是可以对原来的代码不做任何改动,完全解耦。缺点是相当于要同时维护两份代码。 Type Hints 实践 基本上,日常编码中常用的 Type Hints 写法都已经介绍给大家了,下面就让我们一起来看看如何在实际编码中中应用 Type Hints。 dataclass——数据类 dataclass 是一个装饰器,它可以对类进行装饰,用于给类添加魔法方法,例如 init() 和 repr() 等,它在 PEP 557[https://www.python.org/dev/peps/pep-0557/]中被定义。 from dataclasses import dataclass, field @dataclass class User(object): id: int name: str friends: list[int] = field(default_factory=list) data = { "id": 123, "name": "Tim", } user = User(**data) print(user.id, user.name, user.friends) # > 123 Tim [] 以上使用 dataclass 编写的代码同如下代码等价: class User(object): def __init__(self, id: int, name: str, friends=None): self.id = id self.name = name self.friends = friends or [] data = { "id": 123, "name": "Tim", } user = User(**data) print(user.id, user.name, user.friends) # > 123 Tim [] 注意:dataclass 并不会对字段类型进行检查。 可以发现,使用 dataclass 来编写类可以减少很多重复的样板代码,语法上也更加清晰。 Pydantic Pydantic 是一个基于 Python Type Hints 的第三方库,它提供了数据验证、序列化和文档的功能,是一个非常值得学习借鉴的库。以下是一段使用 Pydantic 的示例代码: from datetime import datetime from typing import Optional from pydantic import BaseModel class User(BaseModel): id: int name = 'John Doe' signup_ts: Optional[datetime] = None friends: list[int] = [] external_data = { 'id': '123', 'signup_ts': '2021-09-02 17:00', 'friends': [1, 2, '3'], } user = User(**external_data) print(user.id) # > 123 print(repr(user.signup_ts)) # > datetime.datetime(2021, 9, 2, 17, 0) print(user.friends) # > [1, 2, 3] print(user.dict()) """ { 'id': 123, 'signup_ts': datetime.datetime(2021, 9, 2, 17, 0), 'friends': [1, 2, 3], 'name': 'John Doe', } """ 注意:Pydantic 会对字段类型进行强制检查。 Pydantic 写法上跟 dataclass 非常类似,但它做了更多的额外工作,还提供了如 .dict() 这样非常方便的方法。 再来看一个 Pydantic 进行数据验证的示例,当 User 类接收到的参数不符合预期时,会抛出 ValidationError 异常,异常对象提供了 .json() 方法方便查看异常原因。 from pydantic import ValidationError try: User(signup_ts='broken', friends=[1, 2, 'not number']) except ValidationError as e: print(e.json()) """ [ { "loc": [ "id" ], "msg": "field required", "type": "value_error.missing" }, { "loc": [ "signup_ts" ], "msg": "invalid datetime format", "type": "value_error.datetime" }, { "loc": [ "friends", 2 ], "msg": "value is not a valid integer", "type": "type_error.integer" } ] """ 所有报错信息都保存在一个 list 中,每个字段的报错又保存在嵌套的 dict 中,其中 loc 标识了异常字段和报错位置,msg 为报错提示信息,type 则为报错类型,这样整个报错原因一目了然。 MySQLHandler MySQLHandler[https://github.com/jianghushinian/python-scripts/blob/main/scripts/mysql_handler_type_hints.py]是我对 pymysql 库的封装,使其支持使用 with 语法调用 execute 方法,并且将查询结果从 tuple 替换成 object,同样也是对 Type Hints 的应用。 class MySQLHandler(object): """MySQL handler""" def __init__(self): self.conn = pymysql.connect( host=DB_HOST, port=DB_PORT, user=DB_USER, password=DB_PASS, database=DB_NAME, charset=DB_CHARSET, client_flag=CLIENT.MULTI_STATEMENTS, # execute multi sql statements ) self.cursor = self.conn.cursor() def __del__(self): self.cursor.close() self.conn.close() @contextmanager def execute(self): try: yield self.cursor.execute self.conn.commit() except Exception as e: self.conn.rollback() logging.exception(e) @contextmanager def executemany(self): try: yield self.cursor.executemany self.conn.commit() except Exception as e: self.conn.rollback() logging.exception(e) def _tuple_to_object(self, data: List[tuple]) -> List[FetchObject]: obj_list = [] attrs = [desc[0] for desc in self.cursor.description] for i in data: obj = FetchObject() for attr, value in zip(attrs, i): setattr(obj, attr, value) obj_list.append(obj) return obj_list def fetchone(self) -> Optional[FetchObject]: result = self.cursor.fetchone() return self._tuple_to_object([result])[0] if result else None def fetchmany(self, size: Optional[int] = None) -> Optional[List[FetchObject]]: result = self.cursor.fetchmany(size) return self._tuple_to_object(result) if result else None def fetchall(self) -> Optional[List[FetchObject]]: result = self.cursor.fetchall() return self._tuple_to_object(result) if result else None 运行期类型检查 Type Hints 之所以叫 Hints 而不是 Check,就是因为它只是一个类型的提示而非真正的检查。上面演示的 Type Hints 用法,实际上都是 IDE 在帮我们完成类型检查的功能,但实际上,IDE 的类型检查并不能决定代码执行期间是否报错,仅能在静态期做到语法检查提示的功能。 要想实现在代码执行阶段强制对类型进行检查,则需要我们通过自己编写代码或引入第三方库的形式(如上面介绍的 Pydantic)。下面我通过一个 type_check 函数实现了运行期动态检查类型,来供你参考: from inspect import getfullargspec from functools import wraps from typing import get_type_hints def type_check(fn): @wraps(fn) def wrapper(*args, **kwargs): fn_args = getfullargspec(fn)[0] kwargs.update(dict(zip(fn_args, args))) hints = get_type_hints(fn) hints.pop("return", None) for name, type_ in hints.items(): if not isinstance(kwargs[name], type_): raise TypeError(f"expected {type_.__name__}, got {type(kwargs[name]).__name__} instead") return fn(**kwargs) return wrapper # name: str = "world" name: int = 2 @type_check def greeting(name: str) -> str: return str(name) print(greeting(name)) # > TypeError: expected str, got int instead 只要给 greeting 函数打上 type_check 装饰器,即可实现运行期类型检查。 附录 如果你想继续深入学习使用 Python Type Hints,以下是一些我推荐的开源项目供你参考: Pydantic [https://github.com/samuelcolvin/pydantic] FastAPI [https://github.com/tiangolo/fastapi] Tornado [https://github.com/tornadoweb/tornado] Flask [https://github.com/pallets/flask] Chia-pool [https://github.com/Chia-Network/pool-reference] MySQLHandler [https://github.com/jianghushinian/python-scripts/blob/main/scripts/mysql_handler_type_hints.py] 推荐阅读 TypeScript 枚举指南 实战经验分享:使用 PyO3 来构建你的 Python 模块

优秀的个人博客,低调大师

Linux入门必备|搭建JAVAEE开发环境

2 --> 搭建JAVAEE开发环境 安装JDK 第一步:先将软件通过xftp5 上传到/opt 下 第二步:解压缩到/opt目录下 第三步:配置环境变量的配置文件vim /etc/profile JAVA_HOME=/opt/jdk1.7.0_79 PATH=$JAVA_HOME/bin:$PATH CLASSPATH=$JAVA_HOME/lib:. export JAVA_HOME PATH CLASSPATH 第四步:使配置文件生效 第五步:测试安装成功 第六步:在目录/home/zhangsan下编写HelloWorld.java编译并运行 安装Tomcat 第一步:先将软件通过xftp5 上传到/opt 下 第二步:解压到/opt 第三步:启动tomcat 第四步:Linux上访问tomcat 第五步:windows上访问tomcat 注意:从其它机器上访问需要关闭linux的防火墙。 安装mysql 第一步:查看是否已经安装了mariadb 检查linux是否安装了mariadb数据库,mariadb数据库是mysql的分支。是免费开源的。mariadb和msyql会有冲突。首先要检查安装了mariadb, 卸载掉。 检查命令:yum list installed | grep mariadb 卸载命令:yum –y remove xxxx *如果卸载不成功,需要去掉参数-y,手动确认卸载。 第二步:上传mysql安装包到/opt下 第三步:解压mysql安装包到目录/opt 第四步:修改解压后的根目录名 第五步:创建数据文件夹data data文件夹是mysql用来存放数据库文件的,数据库的表数据都放在data目录。 默认没有data目录,可以手工创建data目录,在mysql-5.7.18文件夹目录下创建一个data文件夹。 第六步:创建用来执行mysqld命令的Linux用户 创建mysql用户,用来执行MySQL的命令mysqld ,此命令用来初始化msyql基础信息。可以使用其他用户,例如叫做 mydb.等,但不推荐。 第七步:初始化mysql 使用mysql的 mysqld 命令初始化数据库的基本信息。切换到mysql-5.7.18/bin目录下执行。 命令:./mysqld --initialize --user=mysql --datadir=/opt/mysql-5.7.18/data --basedir=/opt/mysql-5.7.18 参数说明: --initialize 初始化mysql,创建mysql的root, 随机生成密码。记住密码,登录msyql使用。 --user执行msyqld 命令的linux用户名 --datadir : mysql数据文件的存放位置,目录位置参照本机的设置。 --basedir : msyql安装程序的目录,目录位置参照本机的设置。 该命令执行后,会生成一个临时的mysql数据库root用户的密码,请先拷贝出来记住,后续第一次登录mysql需要使用 密码:iGT#A:CfF7i( 第八步:启用安全功能 在服务器与客户机之间来回传输的所有数据进行加密。通过证书提供了身份验证机制,mysql命令程序mysql_ssl_rsa_setup提供了开启数据加密功能,生成数字证书。 在mysql-5.7.18/bin目录下执行命令: ./mysql_ssl_rsa_setup --datadir=/opt/mysql-5.7.18/data 第九步:修改mysql安装目录权限 mysql安装后,需要更改mysql-5.7.18整个文件夹目录权限,更改所属的用户和组为之前创建的mysql用户及其所在组。在mysql安装目录的上级(/opt)位置,执行命令chown . 例如: chown -R mysql:mysql /opt/mysql-5.7.18/ chmod 777 /opt/mysql-5.7.18/ 第十步:启动mysql 启动MySQL服务,mysql-5.7.18/bin目录下执行命令:./mysqld_safe &(其中&符号表示后台启动),输入命令后按Enter。 确认msyql是否启动,查看进程 ,使用ps -ef | grep mysql 第十一步:客户端登录mysql 登录进入mysql,mysql-5.7.18/bin目录下执行命令:./mysql -uroot -p -u表示使用root用户登录系统,使用第7步生成的密码。 -p表示使用密码登录 第十二步:修改root密码 第8步的root用户密码是临时密码,要修改才能使用。 执行sql语句 show databases; 第一次使用将会提示修改mysql的root用户密码: 修改mysql的密码,命令语法:alter user '用户名'@'主机域名或ip' identified by '新密码' 例如:alter user 'root'@'localhost' identified by 'yf123'; 第十三步:授权远程访问 授权远程访问,在没有授权之前只能在本机访问msyql,远程授权就是让其他计算机通过网络访问mysql(这样远程客户端才能访问)。 授权命令:grant 语法: grant all privileges on *.* to root@'%' identified by 'yf123'; 参数: 其中*.* 的第一个*表示所有数据库名,第二个*表示所有的数据库表; root@'%' 中的root表示用户名,%表示ip地址,%也可以指定具体的ip地址,比如root@localhost,root@192.168.235.130等。 执行授权命令:grant all privileges on *.* to root@'%' identified by 'yf123'; 更新权限信息,执行flush刷新权限: flush privileges; 关闭防火墙:systemctl stop firewalld 远程连接数据库: 第十四步:关闭mysql服务 mysql-5.7.18/bin目录下执行:./mysqladmin -uroot -p shutdown输入密码关闭 查看mysql进程,已经没有mysqld_safe 第十五步:修改数据库编码 查看数据库编码:show variables where Variable_name like '%char%'; 修改mysql的字符集:在mysql客户端执行如下命令

优秀的个人博客,低调大师

WPF自学入门(五)WPF依赖属性

在.NET中有事件也有属性,WPF中加入了路由事件,也加入了依赖属性。最近在写项目时还不知道WPF依赖属性是干什么用的,在使用依赖项属性的时候我都以为是在用.NET中的属性,但是确实上不是的,通过阅读文章和看WPF的书籍已经了解了WPF的依赖属性的使用,我们今天就来看看为什么WPF中要加入依赖属性? 一、什么是依赖属性 WPF中的依赖属性有别于.NET中的属性,因为在WPF中有几个很重要的特征都是需要依赖项属性的支持,例如数据绑定,动画,样式设置等。WPF绝大多数属性都是依赖项属性,只不过它是用了普通的.NET属性过程进行了包装,通过这种包装,就可以像使用属性一样使用依赖项属性了,在后面会说一下怎么通过这种方式包装的。这就使用了旧技术来包装新技术的设计理念就不会干扰.NET。WPF中的依赖属性主要有以下三个优点: 1、依赖属性加入了属性变化通知、限制、验证等功能。这样可以使我们更方便地实现应用,同时大大减少了代码量。 2、节约内存:在WinForm中,每个UI控件的属性都赋予了初始值,这样每个相同的控件在内存中都会保存一份初始值。而WPF依赖属性很好地解决了这个问题,它内部实现使用哈希表存储机制,对多个相同控件的相同属性的值都只保存一份。 3、支持多种提供对象:可以通过多种方式来设置依赖属性的值。可以配合表达式、样式和绑定来对依赖属性设置值。 刚才我们一直在说属性,先来看看属性是什么吧。先创建一个类Person,里面有name属性。 public class Person { publicstring name{set;get;} } 上面就是创建好的属性,看着是不是很简单。属性的创建就是这么简单,在我们想要使用这个类的地方初始化就能用。 既然说WPF中绝大多数的属性都是依赖项属性,我看了一下依赖属性怎么进行创建。 1、依赖属性的所在类型继承自DependencyObject类。 2、使用publicstatic 声明一个DependencyProperty的变量,该变量就是真正的依赖属性。 3、类型的静态构造函数中通过Register方法完成依赖属性的元数据注册。 4、提供依赖属性的包装属性,通过这个属性来完成对依赖属性的读写操作。 创建代码如下: //依赖属性必须在依赖对象DependencyObject Public class Person : DependencyObject { //CLR属性包装器,使得依赖属性NameProperty在外部能够像普通属性那样使用 publicstring Name { get{ return (string)GetValue(NameProperty); } set{ SetValue(NameProperty, value); } } //依赖属性必须为static readonly //DependencyProperty.Register 参数说明 //第一个参数是string类型的,是属性名。 //第二个参数是这个依赖项属性的类型。 //第三个参数是这个拥有这个依赖项属性的类型。 //第四个参数是具有附加属性设置的FramWorkPropertyMetadata对象。 publicstatic readonly DependencyProperty NameProperty = DependencyProperty.Register("Name",typeof(string), typeof(Person), new PropertyMetadata("DefaultName")); } 从上面代码可以看出,依赖属性是通过调用DependencyObject的GetValue和SetValue来对依赖属性进行读写的。它使用哈希表来进行存储的,对应的Key就是属性的HashCode值,而值(Value)则是注册的DependencyPropery;而C#中的属性是类私有字段的封装,可以通过对该字段进行操作来对属性进行读写。属性是字段的包装,WPF中使用属性对依赖属性进行包装。 二、依赖属性的优先级 WPF属性系统提供一种强大的方法,使得依赖属性的值由多种因素决定,从而实现诸如实时属性验证、后期绑定以及向相关属性发出有关其他属性值发生更改的通知等功能。 用来确定依赖属性值的确切顺序和逻辑相当复杂。 了解此顺序有助于避免不必要的属性设置,并且还有可能澄清混淆,使你正确了解为何某些影响或预测依赖属性值的尝试最终却没有得出所期望的值。依赖属性可以在多个位置“设置”,界面代码如下: 本地属性集在设置时具有最高优先级,动画值和强制除外。如果在本地设置某个值,你可以期待该值优先得到应用,甚至期待其优先级高于任何样式或控件模板。 在上面示例中,此处Background本地设置为红色。 因此,即使它是隐式样式,否则将会应用于该作用域中的该类型的所有元素,在此作用域中定义的样式不是最高优先级给予Background属性及其值。 如果从该 Button 实例中删除本地值红色,样式将获得优先级,而按钮将从该样式中获得 Background 值。 在该样式中,触发器具有优先级,因此当鼠标位于按钮上时,按钮为蓝色,其他情况下则为绿色。 下面的图是在网上找的依赖属性优先级列表图,大家后面再使用属性时可以留意一下优先级。 三、依赖属性的继承 依赖属性的继承是WPF属性系统的一项功能。属性值继承使元素树中的子元素可以从父元素获取特定属性的值,并继承该值,就如同它是在最近的父元素中任意位置设置的一样。 父元素可能也已通过属性值继承获得了其值,因此系统有可能一直递归到页面根。 属性值继承不是默认属性系统行为;属性必须用特定的元数据设置来建立,以便使该属性对子元素启动属性值继承。 看到上面图片你可能已经发现了问题:StatusBar没有显式设置FontSize值,但它的字体大小没有继承Window.FontSize的值,而是保持了系统的默认值。导致这样的问题是因为并不是所有元素都支持属性值继承的,如StatusBar、Tooptip和Menu控件。另外,StatusBar等控件截获了从父元素继承来的属性,并且该属性也不会影响StatusBar控件的子元素。例如,如果我们在StatusBar中添加一个Button。那么这个Button的FontSize属性也不会发生改变,其值为默认值。 四、自定义依赖属性 如果想要依赖属性继承,我们可以进行自定义依赖属性继承属性值。 自定义属性步骤: 1、创建派生类CustomStackPanl 创建派生类CustomButton 2、在window控件中引入命名空间 xmlns:sys="clr-namespace:System;assembly=mscorlib" 3、在页面添加Button 效果: 五、依赖属性验证和强制功能 在写代码是都会考虑可能发生的错误。在定义属性时,也需要考虑错误设置属性的可能性。对于传统.NET属性,可以在属性的设置器中进行属性值的验证,不满足条件的值可以抛出异常。但对于依赖属性来说,这种方法不合适,因为依赖属性通过SetValue方法来直接设置其值的。然而WPF有其代替的方式,WPF中提供了两种方法来用于验证依赖属性的值。 1、ValidateValueCallback:该回调函数可以接受或拒绝新值。该值可作为DependencyProperty.Register方法的一个参数。 2、CoerceValueCallback:该回调函数可将新值强制修改为可被接受的值。例如某个依赖属性工作年龄的值范围是25到55,在该回调函数中,可以对设置的值进行强制修改,对于不满足条件的值,强制修改为满足条件的值。如当设置为负值时,可强制修改为0。该回调函数PropertyMetadata构造函数参数进行传递。

优秀的个人博客,低调大师

读scss/sass实例项目带你入门

CSS(Cascading Style Sheet)级联样式表,前端必备技能之一。记得刚开始学习使用DIV+CSS布局的时候,有一个很有意思的网站《禅意花园》,通过模仿它开启了CSS设计之美。随着前端技术发展,纯CSS的弊端更加突显,就有后来的CSS预处理器SASS\SCSS、LESS、Stylus等。记得自己第一次接触SCSS的时候,就“一见钟情”,从此在项目中就没有用过纯CSS的方式了。 本文总结一下在项目中经常用到CSS预处理器的特征,趁此先来认识一下SCSS/SASS,LESS本文就不介绍了,大同小异。 文章涉及实例项目代码:https://github.com/QuintionTang/scss-guide SASS/SCSS Sass 是一种 CSS 的预编译语言。提供了变量(variables)、嵌套(nested rules)、 混合(mixins)、 函数(functions)等功能,并且完全兼容 CSS 语法。Sass 能够帮助复杂的样式表更有条理, 并且易于在项目内部或跨项目共享设计。 Sass是一个最初由Hampton Catlin设计并由Natalie Weizenbaum开发的层叠样式表语言。在开发最初版本之后,Weizenbaum和Chris Eppstein继续通过SassScript来继续扩充Sass的功能。SassScript是一个在Sass文件中使用的小型脚本语言。 SASS诞生于2007年,最早也是最成熟的CSS预处理器,拥有ruby社区的支持和compass这一最强大的css框架,第一次接触的就是compass《CSS开发框架》。 SASS/SCSS 区别 sass和scss大致相同的东西,从概念上讲,没有太大区别,是属于sass的两种不同的语法: 项目中具体使用哪种语法,取决于项目成员,没有好坏之分 Sass:缩进语法,看起来有点怪异,更短,没有花括号,没有分号 .element-a color: hotpink .element-b float: left 对应的CSS: .element-a { color: hotpink; } .element-a .element-b { float: left; } scss:css-like 语法,比sass更加贴近 CSS 语法,完全和 CSS 兼容的。 .element-a{ color: hotpink; .element-b{ float: left; } } 对应的CSS: .element-a { color: hotpink; } .element-a .element-b { float: left; } 基本特征 这里讲到的基本特征,只是简单列举了比较常用的特征。如果需要了解更多信息,建议查看官方文档,内容不是很多。 文章的涉及的代码都是基于演示项目scss-guide,基于gulp来构建。 变量 变量是重复利用的常见方式,特别对于CSS来说,变量可以减少很多重复。如可以定义静态资源路径(图片、字体)、颜色、尺寸(宽度、高度等)。scss 使用 $ 符号 作为变量开头。例如: // scss 变量定义 $font-family-base: "Microsoft YaHei", "微软雅黑", "pingfang sc", "宋体", Arial, Helvetica, sans-serif; $font-base-size: 14px; $color: #444; body { font-family: $font-family-base; font-size: $font-base-size; color: $color; } 下面是生成的对应css代码: body { font-family: "Microsoft YaHei", "微软雅黑", "pingfang sc", "宋体", Arial, Helvetica, sans-serif; font-size: 14px; color: #444; } 嵌套 scss 允许使用嵌套 CSS 选择器,嵌套方式 与 HTML 的层次结构相同。scss嵌套语法,可以让css结构更加清晰,可维护性高。 .container { margin: 0px auto; ul, li { margin: 0px; padding: 0px; } } 对应的CSS代码: .container { margin: 0px auto; } .container ul, .container li { margin: 0px; padding: 0px; } 混合(mixins) 在项目开发过程中有很多类名具有相同或者相似的样式,就可以用 scss 中的混合(mixins)来进行代码组织,以减少重复代码。例如一些常见的兼容性代码: @mixin box-shadow($shadowVal...) { -webkit-box-shadow: $shadowVal; box-shadow: $shadowVal; } .card { @include box-shadow($shadow); } 对应的CSS代码: .card { -webkit-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); } 继承(@extend) 继承(@extend)可以从一个选择器到另一个选择器共享一组CSS属性,即让一个css类继承另一个css类。 继承有两种情况: 一种是继承class,定义以.开头,被继承的模块独立存在与css中。 一个继承一个公共的模块,定义以%开头,被继承的模块不会独立存在与css中。 继承class .button { display: inline-block; font-weight: $btn-font-weight; color: $btn-color; text-align: center; vertical-align: middle; user-select: none; background-color: $btn-background-color; border: $btn-border; } .button-noborder { @extend .button; // 继承.button的样式,编译后.button也存在与css中 border: none; &:hover { font-size: 14px; } } 编译成的css如下: .button, .button-noborder { display: inline-block; font-weight: 500; color: #fff; text-align: center; vertical-align: middle; user-select: none; background-color: #007bff; border: 1px solid rgba(0, 123, 255, 0.8); } .button-noborder { border: none; } .button-noborder:hover { font-size: 14px; } 继承公共模块 %card-share { position: relative; display: flex; flex-direction: column; min-width: 0; word-wrap: break-word; background-color: $card-bg; background-clip: border-box; border: $card-border-width solid $card-border-color; border-radius: $card-border-radius; } .card { @extend %card-share; // 继承公共模块%card-share,编译后的css中不存在:card-share @include box-shadow($shadow); } 编译后的 css 如下: .card { position: relative; display: flex; flex-direction: column; min-width: 0; word-wrap: break-word; background-color: #fff; background-clip: border-box; border: 1px solid rgba(0, 0, 0, 0.125); border-radius: 2px; } .card { -webkit-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); } 操作符 scss操作符包括:引用父选择符&、插值运算符#{}、算术操作符、条件操作符、颜色操作符、字符串操作符、逻辑运算符。 引用父选择符 & 引用父选择符&,大部分时候用于伪类的书写。 a { display: inline-block; color: $black; &:hover { text-decoration: none; } &::before { content: ""; } &.menu { margin: 0px 10px; color: $blue; &-item { margin-left: 20px; } } } 编译后的css如下: a { display: inline-block; color: #000000; } a:hover { text-decoration: none; } a::before { content: ""; } a.menu { margin: 0px 10px; color: #007bff; } a.menu-item { margin-left: 20px; } 插值操作符 插值操作符#{},用于拼接字符串。 .picture-bg { background: url("#{$image-path}main.jpg") center no-repeat; } 编译后的css如下: .picture-bg { background: url("./assets/images/main.jpg") center no-repeat; } 算术操作符 算术运算符包括:+、-、/、*、% 使用算术操作符时,参与运算的数据单位必须相同,否则会报错(例如,一个用px,另一个用em): .sidebar { width: $max-width * 0.8 - 100px; height: $max-width % 75; } 编辑后的css为计算后的结果: .sidebar { width: 1052px; height: 15px; } 关系操作符/逻辑运算符 条件操作符号、关系操作符和逻辑运算符一般都在一起使用, 关系操作符包括:等于(==)、不等于(!=)、大于(>)、大于等于(>=)、小于(<)小于等于(<=) 逻辑操作符包括与(and)、或(or)和非(not) img { cursor: zoom + -in; @if ($max-width>=1440px and $min-width>90%) { max-width: 95%; } @else if ($min-width<100%) { min-width: 100%; } } 编译后css如下: img { cursor: zoom-in; max-width: 95%; } 字符串操作符 字符串操作主要是拼接,使用+来拼接字符串。 img { cursor: zoom + -in; } 编译后的CSS: img { cursor: zoom-in; } 指令 scss常见指令包括导入import、媒介查询media。 导入import用于更好的组织scss代码,将不同功能的scss代码组织到独立的文件,如变量声明文件_variables.scss,然后使用import导入到主文件。 @import "./partials/variables"; @import "./partials/mixins"; @import "./partials/common"; 媒介查询media主要用于响应式的定义。 @media screen and (max-width: 1024px) { .container { width: 100%; } } 编译后的css代码如下: @media screen and (max-width: 1024px) { .container { width: 100%; } } 流程操作符 scss常见的流程操作符包括:@if、@if...@else if、@for、@each、@while、if。 @if p { @if 1 + 1 == 2 { border: 1px solid; } @if 5 < 3 { border: 2px dotted; } @if null { border: 3px double; } } 编译后的css代码如下: p { border: 1px solid; } @if @else if .fullbox { @if ($max-width>=1440px and $min-width>90%) { max-width: 95%; } @else if ($min-width<100%) { min-width: 100%; } } 编译后的css代码如下: .fullbox { max-width: 95%; } @for nth(数组名,索引) ul { &.sidenav { $nav-count: 5; $nav-colors: #ff0001, #ff0002, #ff0003, #ff0004, #ff0005; @for $i from 1 through $nav-count { .item-#{$i} { background-color: nth($nav-colors, $i); // 数组的使用 } } } } 编译后的css代码如下: ul.sidenav .item-1 { background-color: #ff0001; } ul.sidenav .item-2 { background-color: #ff0002; } ul.sidenav .item-3 { background-color: #ff0003; } ul.sidenav .item-4 { background-color: #ff0004; } ul.sidenav .item-5 { background-color: #ff0005; } @each @each $icon in excel, ppt, pdf, doc-around { .icon-#{$icon} { background-image: url("#{$image-path}#{$icon}.png"); } } 编译后的css代码如下: .icon-excel { background-image: url("./assets/images/excel.png"); } .icon-ppt { background-image: url("./assets/images/ppt.png"); } .icon-pdf { background-image: url("./assets/images/pdf.png"); } .icon-doc-around { background-image: url("./assets/images/doc-around.png"); } @while $number: 6; @while $number > 0 { .row-#{$number} { width: 2em * $number; } $number: $number - 2; } 编译后的css代码如下: .row-6 { width: 12em; } .row-4 { width: 8em; } .row-2 { width: 4em; } 三元运算符 if 三元运算符if(condition,exprIfTrue,exprIfFalse),在此示例中用到了map-merge合并key/value变量,获取相应key值的方法为:map-get($map, key)。 $sizes: (); $sizes: map-merge( ( 25: 25%, 50: 50%, 75: 75%, 100: 100%, ), $sizes ); @each $size, $length in $sizes { .size-#{$size} { width: $length !important; font-size: if($size % 50 == 0, "14px", "28px"); } } 编译后的css代码如下: .size-25 { width: 25% !important; font-size: "28px"; } .size-50 { width: 50% !important; font-size: "14px"; } .size-75 { width: 75% !important; font-size: "28px"; } .size-100 { width: 100% !important; font-size: "14px"; } 内置函数 scss包括一些内容函数,例如颜色相关的函数,lighten() 与 darken()函数可用于调亮或调暗颜色,opacify()函数使颜色透明度减少,transparent()函数使颜色透明度增加,mix()函数可用来混合两种颜色。 .main-bg { background: mix(red, yellow, 35%); } 编译后的css代码如下: .main-bg { background: #ffa600; } 这里就不展开介绍了,把常见的方法列出来。 字符串函数 向字符串添加引号的quote()、获取字符串长度的string-length()和将内容插入字符串给定位置的string-insert()。 数值函数 percentage()将无单元的数值转换为百分比,round()将数字四舍五入为最接近的整数,min()和max()获取几个数字中的最小值或最大值,random()返回一个随机数。 List 函数 length()返回列表长度,nth()返回列表中的特定项,join()将两个列表连接在一起,append()在列表末尾添加一个值。 Map 函数 map-get()根据键值获取map中的对应值,map-merge()来将两个map合并成一个新的map,map-values()映射中的所有值。 自定义函数 这里将通过代码介绍一下自定义函数,创建自定义函数需要两个scss指令,创建函数@function和函数将返回的值@return。 下面示例用于生成media相关的样式。 $grid-breakpoints: (); $grid-breakpoints: map-merge( ( xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px, ), $grid-breakpoints ); // 设置指定名称的 @media (min-width: 992px),如果$name存在,将设置对应的@media。 @mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) { $min: get-breakpoint($name, $breakpoints); @if $min { @media (min-width: $min) { @content; } } @else { @content; } } // 获取断点指定名称的断点,如不存在返回空。默认的断点为$grid-breakpoints @function get-breakpoint($name, $breakpoints: $grid-breakpoints) { $min: map-get($breakpoints, $name); @return if($min != 0, $min, null); } .container-p-x { padding-right: $container-padding-x-sm !important; padding-left: $container-padding-x-sm !important; @include media-breakpoint-up(lg) { padding-right: $container-padding-x !important; padding-left: $container-padding-x !important; } @include media-breakpoint-up(xl) { padding-right: $container-padding-x * 2 !important; padding-left: $container-padding-x * 2 !important; } } 编译后的css代码如下: .container-p-x { padding-right: 1rem !important; padding-left: 1rem !important; } @media (min-width: 992px) { .container-p-x { padding-right: 2rem !important; padding-left: 2rem !important; } } @media (min-width: 1200px) { .container-p-x { padding-right: 4rem !important; padding-left: 4rem !important; } } 注释 scss的注释常见有两种 标准注释: /* comment */,会保留到编译后的文件。 单行注释: // comment,只保留在scss源文件中,编译后被过滤,在css中不可见。 一般在 /* 后面加一个感叹号,表示这是“重要注释”。即使是压缩模式编译,也会保留这行注释,通常可以用于声明版权信息。 /*! author:devpoint */ 总结 本文总结的scss内容基本可以满足大部分的项目,学习scss最好的方式就是通过编译的css来理解scss的逻辑,特别是函数。

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册