市面上有5种常见的电商模式 B2B、B2C、C2B、C2C、O2O
B2B(Business to Business),是指商家和商家建立的商业关系,如阿里巴巴
B2C(Business to Consumer) 就是我们经常看到的供应商直接把商品卖给用户,
即 “商对客” 模式,也就是我们呢说的商业零售,直接面向消费销售产品和服务,
如苏宁易购,京东,天猫,小米商城
C2B (Customer to Business),即消费者对企业,先有消费者需求产生
而后有企业生产,即先有消费者提出需求,后又生产企业按需求组织生产
C2C (Customer to Consumer) 客户之间把自己的东西放到网上去卖 。
如淘宝、咸鱼
O2O 即 Online To Offline,也即将线下商务的机会与互联网
结合在一起,让互联网成为线上交易前台,线上快速支付,线上优质服务,
如:饿了么,美团,淘票票,京东到家
谷粒商城是一个B2C模式的电商平台,销售自营商品给客户
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mUQdvdIc-1651150085131)(https://cdn.jsdelivr.net/gh/Jonny-Chi/picgo_imgs/BLog/202203312050401.png)]
前后分离开发,分为内网部署和外网部署,外网是面向公众访问的。
访问前端项目,可以有手机APP,电脑网页;内网部署的是后端集群,
前端在页面上操作发送请求到后端,在这途中会经过Nginx集群,
Nginx把请求转交给API网关(springcloud gateway)(网关可以根据当
前请求动态地路由到指定的服务,看当前请求是想调用商品服务还是购
物车服务还是检索服务),从路由过来如果请求很多,可以负载均衡地调
用商品服务器中一台(商品服务复制了多份),当商品服务器出现问题也
可以在网关层面对服务进行熔断或降级(使用阿里的sentinel组件),网关
还有其他的功能如认证授权、限流(只放行部分到服务器)等。
到达服务器后进行处理(springboot为微服务),服务与服务可能会相互
调用(使用feign组件),有些请求可能经过登录才能进行(基于OAuth2.0的
认证中心。安全和权限使用springSecurity控制)
服务可能保存了一些数据或者需要使用缓存,我们使用redis集群(分片+哨兵集
群)。持久化使用mysql,读写分离和分库分表。
服务和服务之间会使用消息队列(RabbitMQ),来完成异步解耦,分布式事务
的一致性。有些服务可能需要全文检索,检索商品信息,使用ElaticSearch。
服务可能需要存取数据,使用阿里云的对象存储服务OSS。
项目上线后为了快速定位问题,使用ELK对日志进行处理,使用LogStash收
集业务里的各种日志,把日志存储到ES中,用Kibana可视化页面从ES中检
索出相关信息,帮助我们快速定位问题所在。
在分布式系统中,由于我们每个服务都可能部署在很多台机器,服务和服务
可能相互调用,就得知道彼此都在哪里,所以需要将所有服务都注册到注册
中心。服务从注册中心发现其他服务所在位置(使用阿里Nacos作为注册
中心)。
每个服务的配置众多,为了实现改一处配置相同配置就同步更改,就需要配
置中心,也使用阿里的Nacos,服务从配置中心中动态取配置。
服务追踪,追踪服务调用链哪里出现问题,使用springcloud提供的Sleuth、
Zipkin、Metrics,把每个服务的信息交给开源的Prometheus进行聚合分析,
再由Grafana进行可视化展示,提供Prometheus提供的AlterManager实时
得到服务的警告信息,以短信/邮件的方式告知服务开发人员。
还提供了持续集成和持续部署。项目发布起来后,因为微服务众多,每一个都打
包部署到服务器太麻烦,有了持续集成后开发人员可以将修改后的代码提交到
github,运维人员可以通过自动化工具Jenkins Pipeline将github中获取的代码打
包成docker镜像,最终是由k8s集成docker服务,将服务以docker容器的方式运行。
反映了需要创建的微服务以及相关技术。
前后分离开发。前端项目分为admin-vue(工作人员使用的后台管理系统)、
shop-vue(面向公众访问的web网站)、app(公众)、小程序(公众)
商品服务:商品的增删改查、商品的上下架、商品详情
支付服务
优惠服务
用户服务:用户的个人中心、收货地址
仓储服务:商品的库存
秒杀服务
订单服务:订单增删改查
检索服务:商品的检索ES
中央认证服务:登录、注册、单点登录、社交登录
购物车服务
后台管理系统:添加优惠信息等
前后分离开发,并开发基于vue的后台管理系统
SpringCloud全新的解决方案
应用监控、限流、网关、熔断降级等分布式方案,全方位涉及
透彻讲解分布式事务,分布式锁等分布式系统的难点
压力测试与性能优化
各种集群技术的区别以及使用
CI/CD 使用
熟悉SpringBoot以及常见整合方案
了解SpringCloud
熟悉 git maven
熟悉 linux redis docker 基本操作
了解 html,css,js,vue
熟练使用idea开发项目
熟悉SpringBoot以及常见整合方案
了解SpringCloud
熟悉 git maven
熟悉 linux redis docker 基本操作
了解 html,css,js,vue
熟练使用idea开发项目
微服务架构风格,就像是把一个单独的应用程序开发成一套小服务,每个小
服务运行在自己的进程中,并使用轻量级机制通信,通常是 HTTP API 这些
服务围绕业务能力来构建, 并通过完全自动化部署机制来独立部署,这些服
务使用不同的编程语言书写,以及不同数据存储技术,并保持最低限度的集中式管理
简而言之,拒绝大型单体应用,基于业务边界进行服务微化拆分,每个服务独
立部署运行。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SXbVYLUu-1651150085132)(https://cdn.jsdelivr.net/gh/Jonny-Chi/picgo_imgs/BLog/202203312127361.png)]
集群是个物理状态,分布式是个工作方式
只要是一堆机器,也可以叫做集群,他们是不是一起协作干活,
这谁也不知道。
《分布式系统原理与范型》定义:
分布式系统是若干独立计算机的集合,这些计算机对于用户来说像单个系统
分布式系统 (distributed system) 是建立网络之上的软件系统
分布式是指根据不同的业务分布在不同的地方
集群指的是将几台服务器集中在一起,实现同一业务
例如:京东是一个分布式系统,众多业务运行在不同的机器上,所有业务构成
一个大型的分布式业务集群,每一个小的业务,比如用户系统,访问压力大的
时候一台服务器是不够的,我们就应该将用户系统部署到多个服务器,也就是
每一个业务系统也可以做集群化
分布式中的每一个节点,都可以做集群,而集群并不一定就是分布式的
节点:集群中的一个服务器
在分布式系统中,各个服务可能处于不同主机,但是服务之间不可避免的需要
互相调用,我们称之为远程调用
SpringCloud中使用HTTP+JSON的方式来完成远程调用
分布式系统中,A 服务需要调用B服务,B服务在多台机器中都存在,
A调用任意一个服务器均可完成功能
为了使每一个服务器都不要太或者太闲,我们可以负载均衡调用每一个服务器,
提升网站的健壮性
常见的负载均衡算法:
轮询:为第一个请求选择健康池中的每一个后端服务器,然后按顺序往后依
次选择,直到最后一个,然后循环
最小连接:优先选择链接数最少,也就是压力最小的后端服务器,在会话较
长的情况下可以考虑采取这种方式
A服务调用B服务,A服务不知道B服务当前在哪几台服务器上有,哪些正常
的,哪些服务已经下线,解决这个问题可以引入注册中心
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0krXGJL2-1651150085133)(https://cdn.jsdelivr.net/gh/Jonny-Chi/picgo_imgs/BLog/202203312132193.png)]
如果某些服务下线,我们其他人可以实时的感知到其他服务的状态,
从而避免调用不可用的服务
每一个服务最终都有大量配置,并且每个服务都可能部署在多个服务
器上,我们经常需要变更配置,我们可以让每个服务在配置中心获取
自己的配置。
配置中心用来集中管理微服务的配置信息
在微服务架构中,微服务之间通过网络来进行通信,存在相互依赖,
当其中一个服务不可用时,有可能会造成雪崩效应,要防止这种情
况,必须要有容错机制来保护服务
rpc
情景:
订单服务 --> 商品服务 --> 库存服务
库存服务出现故障导致响应慢,导致商品服务需要等待,可能等到10s后库存服
务才能响应。库存服务的不可用导致商品服务阻塞,商品服务等的期间,订单服
务也处于阻塞。一个服务不可用导致整个服务链都阻塞。如果是高并发,第一个
请求调用后阻塞10s得不到结果,第二个请求直接阻塞10s。更多的请求进来导致
请求积压,全部阻塞,最终服务器的资源耗尽。导致雪崩
1、服务熔断
设置服务的超时,当被调用的服务经常失败到达某个阈值,我们可以开启断路保
护机制,后来的请求不再去调用这个服务,本地直接返回默认的数据
2、服务降级
在运维期间,当系统处于高峰期,系统资源紧张,我们可以让非核心业务降级
运行,降级:某些服务不处理,或者简单处理【抛异常,返回NULL,调用
Mock数据,调用 FallBack 处理逻辑】
在微服务架构中,API Gateway 作为整体架构的重要组件,抽象服务中
需要的公共功能,同时它提供了客户端负载均衡,服务自动熔断,灰度发布,统
一认证,限流监控,日志统计等丰富功能,帮助我们解决很多API管理的难题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zTgPD88G-1651150085134)(https://cdn.jsdelivr.net/gh/Jonny-Chi/picgo_imgs/BLog/202203312137369.png)]
安装下面这些软件之前建议设置一下Linux的时间
// 查看当前时间 date // 安装ntpdate yum install -y ntpdate // 更新时间 ntpdate 0.asia.pool.ntp.org // 将系统时间同步到硬件,防止系统重启后时间被还原 hwclock --systohc // 设置定时任务自动更新时间(每5分钟执行一次) echo "* 5 * * * /usr/sbin/ntpdate ntp.api.bz > /dev/null 2>&1" >> /var/spool/cron/root //查看当前Linux有哪些定时任务 crontab -l //重启定时任务服务 systemctl restart crond.service
1 卸载系统之前的docker
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
2 安装一些必要的系统工具
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
3 添加软件源信息
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
4 更新 yum 缓存
sudo yum makecache fast
5 安装DOCKER引擎
sudo yum install docker-ce docker-ce-cli containerd.io
6 启动 Docker 后台服务
sudo systemctl start docker
// 设置开机自启
sudo systemctl enable docker
7. Docker配置国内镜像源
vi /etc/docker/daemon.json
添加以下配置
{
"registry-mirrors": [
"https://registry.docker-cn.com",
"http://hub-mirror.c.163.com",
"https://docker.mirrors.ustc.edu.cn"
]
}
重新加载配置
sudo systemctl daemon-reload
重新启动docker
sudo systemctl restart docker
// 停止
sudo systemctl stop docker
// 重启
sudo systemctl restart docker
// 查看状态
sudo systemctl status docker
// 设置开机自启
sudo systemctl enable docker
// 取消开机自启
sudo systemctl disable docker
// 查看是否已经配置开机自启
sudo systemctl is-enabled docker
查看镜像
sudo docker images
拉取镜像
docker pull 镜像名称:版本号 如:
sudo docker pull redis:5.0.4
删除镜像
sudo docker rmi 镜像ID
运行镜像
sudo docker run --name test -p 80:80 -p 8081:8081 -v /data/log:/data/log --restart=always -d test:1.0.0
name
表示运行的容器的别名
p
宿主机的端口域容器端口的映射关系
v
文件挂载
时间同步
-v /etc/localtime:/etc/localtime
restart
重启方式,自动重启
d
后台运行
镜像打标签
sudo docker tag 基础镜像:版本 镜像地址(默认docker仓库)/路径/镜像名称:版本 如:
sudo docker tag redis:5.0.4 hub.c.163.com/test/redis:5.0.4
推送镜像
sudo docker push hub.c.163.com/test/redis:5.0.4
查看容器运行情况
sudo docker ps
查看所有的容器运行情况
sudo docker ps -a
查询特定的容器
sudo docker ps|grep redis
停止容器
sudo docker stop 容器id
重启容器
sudo docker restart 容器id
启动容器
sudo docker start 容器id
删除容器
sudo docker rm 容器id
查看容器运行的日志
sudo docker logs -f 容器ID
Docker运行日志保存的目录
cd /var/lib/docker/containers
清理Docker容器运行的日志
#首先进入上面的目录
#cd进入对应的容器
#清空 容器id-json.log的记录文件即可清理容器运行的日志
#或者停止、删除、重启镜像
进入容器
sudo docker exec -it 容器ID /bin/bash
使用exit退出
执行容器内的脚本
以Nginx检查配置文件是否正确测试
sudo docker exec 容器ID /usr/sbin/nginx -s reload
方式一(推荐)
// 直接下载
// github的源
sudo curl -L https://github.com/docker/compose/releases/download/1.25.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
// Daocloud镜像
curl -L https://get.daocloud.io/docker/compose/releases/download/1.25.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
// 下载完成之后设置权限
sudo chmod +x /usr/local/bin/docker-compose
// 卸载
sudo rm /usr/local/bin/docker-compose
方式二
#依赖python 可能受python版本的影响导致安装失败
yum install epel-release
yum install -y python-pip
pip install docker-compose
基础命令
#启动容器,如果镜像不存在则先下载镜像,如果容器没创建则创建容器,如果容器没启动则启动
docker-compose up -d
#停止并移除容器
docker-compose down
#重启服务
docker-compose restart
异常情况
Could not find a version that satisfies the requirement requests<3,>=2.20.0 (from docker-compose) (from versions: )
No matching distribution found for requests<3,>=2.20.0 (from docker-compose)
You are using pip version 8.1.2, however version 19.3.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
pip install --upgrade pip
// 升级成功即可安装
容器的日志查看
sudo docker logs -f 容器ID
容器日志保存的位置
/var/lib/docker/containers/
以上路径下保存了当前容器的各种资源及配置,让容器被删除的时候,这里对应的文件夹也会被删除
以下红色部分即为容器运行时的日志
带来的问题
当容器不删除重启的时候,这里的日志文件将会变得越来越大,占用了大量的内存资源;而实际的情况下,以SpringBoot项目为例,我们都会在项目里面定义自己的日志策略,因此容器运行时记录的运行日志实际上是一份多余的日志;既然是日志,必定就会占用IO,IO又是一个比较耗时的操作,而且文件越大,操作的性能就会下降。既然没啥用,那我们何不关掉它。
配置daemon.json
vim /etc/docker/daemon.json
#添加以下配置
"log-driver":"none"
#重启docker
service docker restart
移除并重新运行镜像,再来查看容器日志,发现*-json.log已经没有了
个人的建议是,测试环境是可以开启这个容器日志,如果镜像测试一旦稳定,推到生产的时候,出于性能考虑(因为这部分日志没有太大的价值),所以可以直接关闭掉;
配置daemon.json
vim /etc/docker/daemon.json
#添加以下配置
"log-driver":"json-file",
"log-opts": {"max-size":"1m", "max-file":"3"}
不太建议使用这种方式,虽然可以解决问题,但是我认为不是一个最好的方法
创建清除日志的脚本
vim /etc/docker/clean_docker_container_logs.sh
#!/bin/sh
echo "======== start clean docker containers logs ========"
#查找/var/lib/docker/containers/路径下以-json.log结尾的文件
logs=$(find /var/lib/docker/containers/ -name *-json.log)
for log in $logs
do
echo "clean logs : $log"
#将对应文件的内容置为null
cat /dev/null > $log
# 或者
# echo "">$log
done
echo "======== end clean docker containers logs ========"
然后在crontab设置定时任务执行
#5分钟执行一次
*/5 * * * * sh /etc/docker/clean_docker_container_logs.sh
1 拉去mysql镜像
sudo docker pull mysql:5.7
2 启动mysql容器
# --name指定容器名字 -v目录挂载 -p指定端口映射 -e设置mysql参数 -d后台运行
sudo docker run --name mysql -v /opt/docker/mysql/data:/var/lib/mysql -v /opt/docker/mysql/config:/etc/mysql -v /opt/docker/mysql/log:/var/log/mysql -e MYSQL_ROOT_PASSWORD=root -p 3306:3306 -d mysql:5.7
3 使用sudo su - root(切换为root,这样就不用每次都sudo来赐予了)
sudo su - root
4 进入mysql容器
docker exec -it 容器名称|容器id bin/bash
#1 在docker hub搜索redis镜像
docker search redis
#2 拉取redis镜像到本地
docker pull redis:6.0.10
#3 修改需要自定义的配置(docker-redis默认没有配置文件,
自己在宿主机建立后挂载映射)
#创建并修改/opt/docker/redis/conf/redis.conf
mkdir -p /opt/docker/redis/conf
vi /opt/docker/redis/conf/redis.conf
bind 0.0.0.0 开启远程权限(暂时可以不配置)
appendonly yes 开启aof持久化
#4 启动redis服务运行容器
docker run -p 6379:6379 --name redis -v /opt/docker/redis/data:/data -v /opt/docker/redis/conf/redis.conf:/etc/redis/redis.conf -d redis redis-server /etc/redis/redis.conf
解释:-v /opt/docker/redis/data:/data # 将数据目录挂在到本地保证数据安全
-v /opt/docker/redis/conf/redis.conf:/etc/redis/redis.conf # 将配置文件挂在到本地修改方便
#5 直接进去redis客户端。
docker exec -it redis redis-cli
<mirrors>
<mirror>
<id>nexus-aliyunid>
<mirrorOf>centralmirrorOf>
<name>Nexus aliyunname>
<url>http://maven.aliyun.com/nexus/content/groups/publicurl>
mirror>
mirrors>
<profiles>
<profile>
<id>jdk-1.8id>
<activation>
<activeByDefault>trueactiveByDefault>
<jdk>1.8jdk>
activation>
<properties>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
<maven.compiler.compilerVersion>1.8maven.compiler.compilerVersion>
properties>
profile>
profiles>
vscode
Auto Close Tag
Auto Rename Tag
Chinese
ESlint
HTML CSS Support
HTML Snippets
JavaScript (ES6) code snippets
Live Server
open in brower
Vetur
idea
lombok、mybatisx
进行具体配置之前,建议对git不太了解的朋友参考下面内容
查看git相关配置
查看配置 :git config -l
查看系统配置:git config --system --list #配置文件所在路径在Git安装目录下的 Git目录下的etc目录的gitconfig
查看当前用户配置:git config --global --list
#配置文件路径:Windows:C:\User\Adminidtrator\.gitconfig Mac:/Users/用户/.gitconfig
设置用户名与邮箱
- 通过命令方式修改用户及邮箱
查看是否已经设置过,用户名及邮箱
git config user.name git config user.email
若已设置,执行下面命令,重设
git config --global --unset user.name git config --global --unset user.email
重置后,重新设置用户名及密码
git config --global user.name "xxx" // 配置全局用户名,如Github上注册的用户名 git config --global user.email "[email protected]" // 配置全局邮箱,如Github上配置的邮箱
- 通过配置文件直接修改用户和邮箱
- 对每个账户生成一对密钥
首先进入保存秘钥的目录:
cd ~/.ssh // 进入目录,该目录下保存生成的秘钥
然后,根据账户邮箱生成秘钥。例如我在GitHub上的邮箱是
******.**.**
,则命令为:
ssh-keygen -t rsa -C "******.**.**"
输入完成后,会有如下提示:
Generating public/private rsa key pair. Enter file in which to save the key (/Users/jonny/.ssh/id_rsa):
这里要求对秘钥进行命名,默认的文件名是
id_rsa
。为了方便区分,我这里命名为id_rsa_github
。接下来的提示都直接进行回车,直到秘钥生成。通过ls
命令,可以看到刚刚生成的密钥对id_rsa_github
和id_rsa_github.pub
。其中id_rsa_github.pub
是公钥。同样,对于GitLab上的账户,我是用另一个邮箱注册的,按照同样的步骤生成
id_rsa_gitlab
的秘钥对。接下来的步骤,除额外说明外,两个账户的操作完全相同查看生成的密钥
cat id_rsa_gitee.pub
这里的id_rsa_gitee是我上面自定义的文件名,这里可根据自己设置的文件名修改
- 私钥添加到本地
SSH协议的原理,就是在托管网站上使用公钥,在本地使用私钥,这样本地仓库就可以和远程仓库进行通信。在上一步已经生成了秘钥文件,接下来需要使用秘钥文件,首先是在本地使用秘钥文件:
ssh-add ~/.ssh/id_rsa_github // 将GitHub私钥添加到本地 ssh-add ~/.ssh/id_rsa_gitlab // 将GitLab私钥添加到本地
为了检验本地是否添加成功,可以使用
ssh-add -l
命令进行查看
- 对本地秘钥进行配置
由于添加了多个密钥文件,所以需要对这多个密钥进行管理。在
.ssh
目录下新建一个config文件:
touch config
文件中的内容如下:
Host github // 网站的别名,随意取 HostName github.com // 托管网站的域名 User Jonny // 托管网站上的用户名 IdentityFile ~/.ssh/id_rsa_github // 使用的密钥文件 // Gitee的配置相同 Host gitee HostName gitee.com User Jonny IdentityFile ~/.ssh/id_rsa_gitee
注意,实际写到配置文件中时,一定不要写//的注释,建议直接不写注释
- 公钥添加到托管网站
以GitHub为例,先在本地复制公钥。进入
.ssh
目录,使用vim id_rsa_github.pub
查看生成的GitHub公钥,全选进行复制。登录GitHub,点击右上角头像选择
settings
,在打开的页面中选择SSH and GPG keys[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GGAPoe7h-1651150085137)(https://cdn.jsdelivr.net/gh/Jonny-Chi/picgo_imgs/BLog/202204010029377.png)]
在打开的页面的Key输入框中粘贴刚刚复制的公钥,title的名字自己随便去,然后点击下方的
Add SSH key
按钮:至此,托管网站的公钥添加完成。总结来说,就是针对每个托管网站分别生成一对密钥,然后分别添加到本地和托管网站。
这时候,可以测试一下配置是否成功,测试命令使用别名。例如,对于GitHub,本来应该使用的测试命令是:
ssh -T [email protected]
在config文件中,给GitHub网站配置的别名就是github,所以直接使用别名,就是
ssh -T git@github
使用有两种情况,一种情况是从远端拉取代码到本地,一种是本地已有仓库需要与远程仓库关联。
选择SSH协议的复制命令,如对于GitLab上代码库test,其复制命令为
git clone [email protected]:****/test.git
由于使用了别名gitlab,所以实际使用的复制命令应当为:
git clone git@gitlab:****/test.git
github同理
这种方法较为简单,修改后的代码无需额外配置,可以直接push
这种情况适用于本地新建的仓库需要与远端进行关联,或者之前已经使用sourceTree等图形界面软件拷贝的仓库。进入本地仓库文件夹,需要单独配置该仓库的用户名和邮箱
git config user.name "Jonny" git config user.email "******.**.**"
然后,进入本地仓库的git目录,打开config文件
cd .git // 该目录是隐藏的,ls命令不可见,但是可以直接进入,如果是新建的文件夹需要先执行git init vim config
在config文件中,修改(config文件中已有remote "origin"信息)或者添加(config文件中不包含remote "origin"信息)分支信息:
[remote "origin"] url = git@gitlab:GuiLiu/test.git fetch = +refs/heads/*:refs/remotes/origin/*
主要是URL部分,原生的信息一般是[email protected]:GuiLiu/test.git
,需要将gitlab.com使用别名gitlab代替。
可以看到,仓库中的关键是要配置好用户名和邮箱,以及使用别名。使用别名的目的是为了通过别名,将本地仓库与密钥目录.ssh
文件夹下的密钥进行管理,这样就完成了本地仓库使用的私钥与托管网站使用的公钥的配对,而用户名和邮箱是该仓库使用SSH协议时需要用到的信息
在关联coding上托管的代码时,遇到了一点麻烦,主要是因为别名的修改不正确,以及20端口禁用的问题导致的,所以单独记录下,.ssh
目录下的config文件中的密钥信息应该为:
Host coding
HostName git-ssh.coding.net // 这个域名使用coding官网获取的,不能写coding.net
User liugui
IdentityFile ~/.ssh/id_rsa_coding
Port 443 // 20端口可能被禁用,需要使用443端口
注意,实际写到配置文件中时,一定不要写//的注释,建议直接不写注释
# 配置用户名
git config --global user.name "username" //(名字,自定义,提交代码时所看到的用户名)
# 配置邮箱
git config --global user.email "[email protected]" // 注册账号时使用的邮箱
# 配置ssh免密登录
ssh-keygen -t rsa -C "[email protected]"
三次回车后生成了密钥,也可以查看密钥
cat ~/.ssh/id_rsa.pub
浏览器登录码云后,个人头像上点设置、然后点ssh公钥、随便填个标题,然后赋值
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6MWhGXSKdRxr1mGPZysDrcwABMTrxc8Va2IWZyIMMRHH9Qn/wy3PN2I9144UUqg65W0CDE/thxbOdn78MygFFsIG4j0wdT9sdjmSfzQikLHFsJ02yr58V6J2zwXcW9AhIlaGr+XIlGKDUy5mXb4OF+6UMXM6HKF7rY9FYh9wL6bun9f1jV4Ydlxftb/xtV8oQXXNJbI6OoqkogPKBYcNdWzMbjJdmbq2bSQugGaPVnHEqAD74Qgkw1G7SIDTXnY55gBlFPVzjLWUu74OWFCx4pFHH6LRZOCLlMaJ9haTwT2DB/sFzOG/Js+cEExx/arJ2rvvdmTMwlv/T+6xhrMS3 [email protected]
# 测试
ssh -T [email protected]
# 测试成功
Hi unique_perfect! You've successfully authenticated, but GITEE.COM does not provide shell access.
在码云新建仓库,仓库名gulimall,选择语言java,在.gitignore选中maven,
许可证选Apache-2.0,开发模型选生产/开发模型,开发时在dev分支,
发布时在master分支,创建如图所示
在IDEA中New Project from version control Git 复制刚才项目的地址,如https://gitee.com/***/gulimall.git
创建以下模块
商品服务product
存储服务ware
订单服务order
优惠券服务coupon
用户服务member
每个模块导入web和openFeign
如下图所示
按照上图的格式创建即可
创建父模块:在gulimall中创建pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gulimall</name>
<description>谷粒商城-聚合服务</description>
<packaging>pom</packaging>
<modules>
<module>gulimall-coupon</module>
<module>gulimall-member</module>
<module>gulimall-order</module>
<module>gulimall-product</module>
<module>gulimall-ware</module>
</modules>
</project>
修改总项目的.gitignore,把小项目里的垃圾文件在提交的时候忽略掉
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
**/mvnw
**/mvnw.cmd
**/.mvn
**/target
.idea
**/.gitignore
**/README.md
#创建数据库之前需要启动docker服务
sudo docker ps
sudo docker ps -a
# 这两个命令的差别就是后者会显示 【已创建但没有启动的容器】
# 我们接下来设置我们要用的容器每次都是自动启动
sudo docker update redis --restart=always
sudo docker update mysql --restart=always
# 如果不配置上面的内容的话,我们也可以选择手动启动
sudo docker start mysql
sudo docker start redis
# 如果要进入已启动的容器
sudo docker exec -it mysql /bin/bash
接着创建数据库
然后接着去sqlyog直接我们的操作,在左侧root上右键建立数据库:
字符集选utf8mb4,他能兼容utf8且能解决一些乱码的问题。分别
建立了下面数据库
gulimall_oms
gulimall_pms
gulimall_sms
gulimall_ums
gulimall_wms
所有的数据库数据再复杂也不建立外键,因为在电商系统里,数据量大,
做外键关联很耗性能。
具体SQL在代码中有,不一一列举出来了
//在码云上搜索人人开源,我们使用renren-fast,renren-fast-vue项目。
git clone https://gitee.com/renrenio/renren-fast.git
git clone https://gitee.com/renrenio/renren-fast-vue.git
//下载到了桌面,我们把renren-fast移动到我们的项目文件夹(删掉.git文件),而renren-fast-vue是用VSCode打开的(后面再弄)
在idea(root)项目里的pom.xml添加一个
<modules>
<module>gulimall-coupon</module>
<module>gulimall-member</module>
<module>gulimall-order</module>
<module>gulimall-product</module>
<module>gulimall-ware</module>
<module>renren-fast</module>
</modules>
然后打开renren-fast/db/mysql.sql,复制全部,在数据库工具直接执行即可
用VSCode打开renren-fast-vue
安装node:
注意:版本为v10.16.3,python版本为3(因为不同版本等下下面遇到的问题可能不一样)【我在这用的是16.14版的】
接下来
npm config set registry http://registry.npm.taobao.org/ # 设置node仓库。提高下载速度
然后在VScode的终端进入项目中输入 npm install,会报错,然后进行如下操作:
报错:
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] install: `node install.js`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the [email protected] install script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! E:\installation_program\nodeRepository\npm_cache\_logs\2021-02-09T07_38_47_075Z-debug.log
解决报错:
npm install chromedriver --chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver
报错:
Module build failed: Error: ENOENT: no such file or directory, scandir '/Users/joe/Study/Demo/gulimall/renren-fast-vue/node_modules/node-sass/vendor'
解决报错
1.cd 进入node_modules
$ cd node_modules
2.运行npm rebuild node-sass
$ npm rebuild node-sass
需要等待一段时间:出现 rebuilt dependencies successfully
最后直接运行代码就OK了
浏览器输入localhost:8001 就可以看到内容了,登录账号admin 密码admin
git clone https://gitee.com/renrenio/renren-generator.git
下载到桌面后,同样把里面的.git文件删除,然后移动到我们IDEA项目目录中,同样配置好pom.xml(root)
<modules>
<module>gulimall-coupon</module>
<module>gulimall-member</module>
<module>gulimall-order</module>
<module>gulimall-product</module>
<module>gulimall-ware</module>
<module>renren-fast</module>
<module>renren-generator</module>
</modules>
修改renren-generator的application.yml
url: jdbc:mysql://192.168.1.17:3306/gulimall-pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
修改generator.properties
mainPath=com.rabbitboss # 主目录
package=com.rabbitboss.gulimall # 包名
moduleName=product # 模块名
author=Jonny # 作者
email=xxx@qq.com # email
tablePrefix=pms_ # 我们的pms数据库中的表的前缀都有pms,
如果写了表前缀,每一张表对于的javaBean就不会添加前缀了
运行RenrenApplication。如果启动不成功,修改application中是port为80。访问http://localhost:80
然后点击全部,点击生成代码。下载了压缩包
解压压缩包,把main放到gulimall-product的同级目录下。
然后在项目上右击(在项目上右击很重要)new modules— maven—然后在name上输入gulimall-common。
在pom.xml中也自动添加了<module>gulimall-common</module>
在common项目的pom.xml(我们把每个微服务里公共的类和依赖放到common里。)中添加
<!-- mybatisPLUS-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<!--简化实体类,用@Data代替getset方法-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<!-- httpcomponent包https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.13</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
然后在product项目中的pom.xml中加入下面内容
<dependency>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
复制
renren-fast----utils包下的Query和PageUtils、R、Constant复制到common项目的java/com.rabbitboss.common.utils下
把@RequiresPermissions这些注解掉,因为是shiro的
复制renren-fast中的xss包粘贴到common的java/com.rabbitboss.common目录下。
还复制了exception文件夹,对应的位置关系自己观察一下就行
注释掉product项目下类中的//import org.apache.shiro.authz.annotation.RequiresPermissions;,他是shiro的东西
注释renren-generator\src\main\resources\template/Controller中所有的
# @RequiresPermissions。
# import org.apache.shiro.authz.annotation.RequiresPermissions;
总之什么报错就去renren-fast里面找。
测试
测试与整合商品服务里的mybatisplus
在common的pom.xml中导入
<!-- 数据库驱动 https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
<!--tomcat里一般都带-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope> # Tomcat有带,所以provided
</dependency>
删掉common里xss/xssfiler和XssHttpServletRequestWrapper
在product项目的resources目录下新建application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.1.17:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
# MapperScan
# sql映射文件位置
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
然后在主启动类上加上注解@MapperScan()
@MapperScan("com.rabbitboss.gulimall.product.dao")
@SpringBootApplication
public class gulimallProductApplication {
public static void main(String[] args) {
SpringApplication.run(gulimallProductApplication.class, args);
}
}
然后去测试,先通过下面方法给数据库添加内容
@SpringBootTest
class GulimallProductApplicationTests {
@Autowired
BrandService brandService;
@Test
void contextLoads() {
// BrandEntity brandEntity = new BrandEntity();
// brandEntity.setBrandId(1L);
// brandEntity.setDescript("华为");
// brandEntity.setDescript("");
// brandEntity.setName("华为");
// this.brandService.save(brandEntity);
// System.out.println("保存成功!");
// brandService.updateById(brandEntity);
// System.out.println("修改成功!");
List<BrandEntity> list = brandService.list(new QueryWrapper<BrandEntity>().eq("brand_id", 1L));
list.forEach((item)->{
System.out.println(item);
});
}
}
重新打开generator逆向工程,修改generator.properties
# 主目录
mainPath=com.rabbitboss
package=com.rabbitboss.gulimall
moduleName=coupon
autho=rabbitboss
email=xxx@qq.com
tablePrefix=sms_
修改yml数据库信息
spring:
datasource:
username: root
password: root
url: jdbc:mysql://192.168.1.17:3306/gulimall_sms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-value: 1
logic-not-delete-value: 0
server:
port: 7000
启动生成RenrenApplication.java,运行后去浏览器80端口查看,同样让他一
页全显示后选择全部后生成。生成后解压复制到coupon项目对应目录下。
让coupon也依赖于common,修改pom.xml
<dependency>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
resources下src包先删除
添加application.yml
spring:
datasource:
username: root
password: root
url: jdbc:mysql://192.168.1.17:3306/gulimall_sms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-value: 1
logic-not-delete-value: 0
运行gulimallCouponApplication.java
http://localhost:8080/coupon/coupon/list
{"msg":"success","code":0,"page":{"totalCount":0,"pageSize":10,"totalPage":0,"currPage":1,"list":[]}}
重新使用代码生成器生成ums
模仿上面修改下面两个配置
代码生成器里:
url: jdbc:mysql://192.168.1.17:3306/gulimall_sms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
mainPath=com.rabbitboss
package=com.rabbitboss.gulimall
moduleName=member
author=rabbitboss
email=xxx@qq.com
tablePrefix=ums_
重启RenrenApplication.java,然后同样去浏览器获取压缩包解压到对应member项目目录
member也导入依赖
<dependency>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
同样新建application.yml
spring:
datasource:
username: root
password: root
url: jdbc:mysql://192.168.1.17:3306/gulimall-ums?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-value: 1
logic-not-delete-value: 0
server:
port: 8000
order端口是9000,product是10000,ware是11000。
以后比如order系统要复制多份,他的端口计算9001、9002。。。
重启web后,http://localhost:8000/member/growthchangehistory/list
测试成功:{"msg":"success","code":0,"page":{"totalCount":0,"pageSize":10,"totalPage":0,"currPage":1,"list":[]}}
修改代码生成器
jdbc:mysql://192.168.1.17:3306/gulimall_oms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
#代码生成器,配置信息
mainPath=com.rabbitboss
package=com.rabbitboss.gulimall
moduleName=order
author=rabbitboss
email=xxx@qq.com
tablePrefix=oms_
运行RenrenApplication.java重新生成后去下载解压放置。
application.yml
spring:
datasource:
username: root
password: root
url: jdbc:mysql://192.168.1.17:3306/gulimall_oms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-value: 1
logic-not-delete-value: 0
server:
port: 9000
在pom.xml添加
<dependency>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
启动gulimallOrderApplication.java
http://localhost:9000/order/order/list
{"msg":"success","code":0,"page":{"totalCount":0,"pageSize":10,"totalPage":0,"currPage":1,"list":[]}}
修改代码生成器
jdbc:mysql://192.168.1.17:3306/gulimall_wms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
#代码生成器,配置信息
mainPath=com.rabbitboss
package=com.rabbitboss.gulimall
moduleName=ware
author=rabbitboss
email=xxx@qq.com
tablePrefix=wms_
运行RenrenApplication.java重新生成后去下载解压放置。
application.yml
spring:
datasource:
username: root
password: root
url: jdbc:mysql://192.168.1.17:3306/gulimall_wms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
logic-delete-value: 1
logic-not-delete-value: 0
server:
port: 11000
在pom.xml添加
<dependency>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
启动gulimallWareApplication.java
http://localhost:11000/ware/wareinfo/list
{"msg":"success","code":0,"page":{"totalCount":0,"pageSize":10,"totalPage":0,"currPage":1,"list":[]}}
<spring-boot.version>2.6.6.RELEASE</spring-boot.version>
<spring-cloud.version>2021.0.1</spring-cloud.version>
在common的pom.xml中加入
# 下面是依赖管理,相当于以后再dependencies里引spring cloud alibaba就不用写版本号, 全用dependencyManagement进行管理
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.0.1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
作为我们的注册中心和配置中心。
先了解一下 Spring Cloud 应用如何接入 Nacos Discovery。
1 首先,修改 common中的pom.xml 文件,引入 Nacos Discovery Starter。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2 在应用的resources中的application.yml 配置文件中配置 Nacos Server 地址和微服务名称
spring:
application:
name: gulimall-coupon
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.1.17:3306/gulimall_sms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
server:
port: 7000
3 我们要配置nacos服务器的地址,也就是注册中心地址,但是我们还没有nacos服务器,所以我们需要启动nacos server创建nacos服务
器(软件官方可以下载)启动命令(standalone代表着单机模式运行,非集群模式)
启动命令windows:startup.cmd -m standalone
启动命令Mac:sh startup.sh -m standalone
停止命令windows:shutdown.cmd
停止命令Mac:sh shutdown.sh
4 使用 @EnableDiscoveryClient 注解开启服务注册与发现功能
package com.rabbitboss.gulimall.coupon;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallCouponApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallCouponApplication.class, args);
}
}
5 访问http://127.0.0.1:8848/nacos/ 账号密码nacos
出现如下页面,则表示访问成功
如图所示:
仿照上面的例子继续配置:member,order,product,ware
想要获取当前会员领取到的所有优惠券。先去注册中心找优惠券服务,注册中心调一台优惠券服务器给会员,
会员服务器发送请求给这台优惠券服务器,然后对方响应。
Feign与注册中心
spring cloud feign
声明式远程调用
feign是一个声明式的HTTP客户端,他的目的就是让远程调用更加简单。
给远程服务发的是HTTP请求。
1 会员服务想要远程调用优惠券服务,只需要给会员服务里引入
openfeign依赖,他就有了远程调用其他服务的能力。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2 在coupon中修改如下的内容
@RequestMapping("coupon/coupon")
public class CouponController {
@Autowired
private CouponService couponService;
@RequestMapping("/member/list")
public R membercoupons(){ //全系统的所有返回都返回R
// 应该去数据库查用户对于的优惠券,但这个我们简化了,不去数据库查了,构造了一个优惠券给他返回
CouponEntity couponEntity = new CouponEntity();
couponEntity.setCouponName("满100减10");//优惠券的名字
return R.ok().put("coupons",Arrays.asList(couponEntity));
}
3 这样我们准备好了优惠券的调用内容
在member的配置类上加注解@EnableFeignClients(basePackages="com.rabbitboss.gulimall.member.feign"),
告诉spring这里面是一个远程调用客户端,member要调用的接口
package com.rabbitboss.gulimall.member;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@MapperScan("com.rabbitboss.gulimall.member.dao")
@EnableDiscoveryClient
@EnableFeignClients(basePackages="com.rabbitboss.gulimall.member.feign")
public class GulimallMemberApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallMemberApplication.class, args);
}
}
4 那么要调用什么东西呢?就是我们刚才写的优惠券的功能,
复制函数部分,在member的com.rabbitboss.gulimall.member.feign包下新建类:
package com.rabbitboss.gulimall.member.feign;
import com.rabbitboss.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient("gulimall-coupon") //告诉spring cloud这个接口是一个远程客户端,要调用coupon服务,再去调用coupon服务/coupon/coupon/member/list对应的方法
public interface CouponFeignService {
@RequestMapping("/coupon/coupon/member/list")
public R membercoupons();//得到一个R对象
}
5 然后我们在member的控制层写一个测试请求
@RestController
@RequestMapping("member/member")
public class MemberController {
@Autowired
private MemberService memberService;
@Autowired
CouponFeignService couponFeignService;
@RequestMapping("/coupons")
public R test(){
MemberEntity memberEntity = new MemberEntity();
memberEntity.setNickname("张三");
R membercoupons = couponFeignService.membercoupons(); //假设张三去数据库查了后返回了张三的优惠券信息
// 打印会员和优惠券信息
return R.ok().put("member",memberEntity).put("coupons",membercoupons.get("coupons"));
}
6 重新启动服务
http://localhost:8000/member/member/coupons
{"msg":"success","code":0,"coupons":[{"id":null,"couponType":null,"couponImg":null,"couponName":"满100减10","num":null,"amount":null,"perLimit":null,"minPoint":null,"startTime":null,"endTime":null,"useType":null,"note":null,"publishCount":null,"useCount":null,"receiveCount":null,"enableStartTime":null,"enableEndTime":null,"code":null,"memberLevel":null,"publish":null}],"member":{"id":null,"levelId":null,"username":null,"password":null,"nickname":"张三","mobile":null,"email":null,"header":null,"gender":null,"birth":null,"city":null,"job":null,"sign":null,"sourceType":null,"integration":null,"growth":null,"status":null,"createTime":null}}
7 上面内容很重要,我们停留5分钟体会一下。
coupon里的R.ok()是什么 # coupon里的控制层就是new了个couponEntity然后放到hashmap(R)里而已。
public class R extends HashMap<String, Object> {
public static R ok() {
return new R();
}
public R put(String key, Object value) {
super.put(key, value);
return this;
}
}
报错:No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?
将gulimall-common下的pom.xml文件中 **spring-cloud-starter-alibaba-nacos-discovery
**的依赖修改一下,
并添加**spring-cloud-starter-loadbalancer
**依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-ribbonartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-loadbalancerartifactId>
<version>3.1.0version>
dependency>
nacos 配置中心不实时同步问题或者读取不到bootstrap.properties
将gulimall-common下的pom.xml文件中添加以下依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bootstrapartifactId>
<version>3.1.0version>
dependency>
/*
* 1、如何使用Nacos作为配置中心统一管理配置
*
* 1)、引入依赖,
*
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
* 2)、创建一个bootstaro.preties文件
* spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
* 3)、给配置中心默认添加一个叫数据集(DataId)gulimall-coupon.properties。默认规则,应用名.properties
* 4)、给应用名.properties添加配置
* 5)、动态获取配置。
* @RefreshScope:动态获取并刷新配置
* @Value("${配置项的名}")
* 如果配置中心和当前应用的配置文件中都配置了相同的项,会优先使用配置中心的配置
*
* 2。细节
* 1).命名空间:配置隔离
* 默认public(保留空间);默认新增所有的配置文件都在public空间
* 1。开发,测试,生产;利用命名空间来做环境隔离。
* 注意,在bootstrap.properties的配置上,需要使用哪个命名空间下的配置
* spring.cloud.nacos.config.namespace=4df4b2fb-a06d-4edc-bd7c-a0fbbc5c87fa
* 2。每一个微服务之间互相隔离配置,每一个微服务都创建自己的命名空间,只加载自己命名空间下的所有配置
* 2).配置集:
* 所有配置的集合
* 3).配置集 ID:
* 类似于配置文件名:即Data ID
* 4).配置分组:
* 默认所有的配置集都属于,"DEFAULT_GROUP"
*
* 每个微服务创建自己的命名空间,dev,test,prod
*
* 3.同时加载多个配置集
* 1)、微服务任何配置信息,任何配置文件,都可以放在配置中心中,
* 2)、只需要在bootstarp.properties中加载配置中心哪些配置文件即可
* 3)、@Value,@ConfigurationProperties ......
* 以前SpringBoot任何方法从配置文件中获取值,都能使用,
* 配置中心有的,优先使用配置中心的配置
* */
我们还可以用nacos作为配置中心。配置中心的意思是不在application.properties等文件中配置了,而是放到nacos配置中心公用,这样无需每台机器都改。
1 引入配置中心依赖,放到common中
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2 在coupons项目中创建/src/main/resources/bootstrap.properties ,这个文件是springboot里规定的,他优先级别比application.properties高
# 改名字,对应nacos里的配置文件名
spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
3 @RestController
@RequestMapping("coupon/coupon")
public class CouponController {
@Autowired
private CouponService couponService;
@Value("${coupon.user.name}")//从application.properties中获取//不要写user.name,他是环境里的变量
private String name;
@Value("${coupon.user.age}")
private Integer age;
@RequestMapping("/test")
public R test(){
return R.ok().put("name",name).put("age",age);
}
}
4 浏览器去nacos里的配置列表,点击+号,data ID:gulimall-coupon.properties,配置
# gulimall-coupon.properties
coupon.user.name="张三"
coupon.user.age=12
5 然后点击发布。重启coupon,http://localhost:7000/coupon/coupon/test
{"msg":"success","code":0,"name":"张三","age":12}
6 但是修改肿么办?实际生产中不能重启应用。在coupon的控制层上加
@RefreshScope
7 最终代码如下
@RefreshScope
@RestController
@RequestMapping("coupon/coupon")
public class CouponController {
@Autowired
private CouponService couponService;
@Value("${coupon.user.name}")//从application.properties中获取//不要写user.name,他是环境里的变量
private String name;
@Value("${coupon.user.age}")
private Integer age;
@RequestMapping("/test")
public R test(){
return R.ok().put("name",name).put("age",age);
}
}
8 重启后,在nacos浏览器里修改配置,修改就可以观察到能动态修改了
nacos的配置内容优先于项目本地的配置内容。
在nacos浏览器中还可以配置:
命名空间:用作配置隔离。(一般每个微服务一个命名空间)
默认public。默认新增的配置都在public空间下
开发、测试、开发可以用命名空间分割。properties每个空间有一份。
在bootstrap.properties里配置
spring.cloud.nacos.config.namespace=b176a68a-6800-4648-833b-be10be8bab00 # 可以选择对应的命名空间 ,即写上对应环境的命名空间ID
也可以为每个微服务配置一个命名空间,微服务互相隔离
配置集:一组相关或不相关配置项的集合。
配置集ID:类似于配置文件名,即Data ID
配置分组:默认所有的配置集都属于DEFAULT_GROUP。自己可以创建分组,比如双十一,618,双十二
spring.cloud.nacos.config.group=DEFAULT_GROUP # 更改配置分组
最终方案:每个微服务创建自己的命名空间,然后使用配置分组区分环境(dev/test/prod)
加载多配置集
我们要把原来application.yml里的内容都分文件抽离出去。我们在nacos里创建好
后,在coupons里指定要导入的配置即可。
bootstrap.properties
spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=ed042b3b-b7f3-4734-bdcb-0c516cb357d7 # # 可以选择对应的命名空间 ,即写上对应环境的命名空间ID
spring.cloud.nacos.config.group=dev # 配置文件所在的组
spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
spring.cloud.nacos.config.ext-config[0].group=dev
spring.cloud.nacos.config.ext-config[0].refresh=true
spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml
spring.cloud.nacos.config.ext-config[1].group=dev
spring.cloud.nacos.config.ext-config[1].refresh=true
spring.cloud.nacos.config.ext-config[2].data-id=other.yml
spring.cloud.nacos.config.ext-config[2].group=dev
spring.cloud.nacos.config.ext-config[2].refresh=true
datasource.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.1.17:3306/gulimall_sms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
mybatis.yml
mybatis-plus:
mapper-locations: classpath:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
other.yml
spring:
application:
name: gulimall-coupon
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
server:
port: 7000
发送请求需要知道商品服务的地址,如果商品服务器有100服务器,1号掉线后,
还得改,所以需要网关动态地管理,他能从注册中心中实时地感知某个服务上
线还是下线。
请求也要加上询问权限,看用户有没有权限访问这个请求,也需要网关。
所以我们使用spring cloud的gateway组件做网关功能。
网关是请求浏览的入口,常用功能包括路由转发,权限校验,限流控制等。springcloud gateway取到了zuul网关。
三大核心概念:
Route: The basic building block of the gateway. It is defined by an ID, a
destination URI, a collection of predicates断言, and a collection of filters.
A route is matched if the aggregate predicate is true.
发一个请求给网关,网关要将请求路由到指定的服务。路由有id,目的地uri,断言的集合,匹配了断言就能到达指定位置,
Predicate断言: This is a Java 8 Function Predicate. The input type is a Spring
Framework ServerWebExchange. This lets you match on anything from the
HTTP request, such as headers or parameters.就是java里的断言函数,匹配请求里的任何信息,包括请求头等
Filter: These are instances of Spring Framework GatewayFilter that have been
constructed with a specific factory. Here, you can modify requests and
responses before or after sending the downstream request.
过滤器请求和响应都可以被修改。
客户端发请求给服务端。中间有网关。先交给映射器,如果能处理就交给handler
处理,然后交给一系列filer,然后给指定的服务,再返回回来给客户端。
1 在pom.xml引入
<dependency>
<groupId>com.atguigu.gulimall</groupId>
<artifactId>gulimall-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
版本环境需保持一致
<spring-boot.version>2.1.8.RELEASE</spring-boot.version>
<spring-cloud.version>Greenwich.SR3</spring-cloud.version>
2 开启注册服务发现@EnableDiscoveryClient
package com.yxj.gulimall.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableDiscoveryClient
public class GulimallGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GulimallGatewayApplication.class, args);
}
}
3 配置nacos注册中心地址applicaion.properties
spring.application.name=gulimall-gateway
spring.cloud.nacos.discovery.server-addr=192.168.11.1:8848
server.port=88
4 bootstrap.properties 填写配置中心地址
spring.application.name=gulimall-coupon
spring.cloud.nacos.config.server-addr=192.168.11.1:8848
spring.cloud.nacos.config.namespace=a791fa0e-cef8-47ee-8f07-5ac5a63ea061
5 nacos里创建命名空间gateway,然后在命名空间里创建文件guilmall-gateway.yml
spring:
application:
name: gulimall-gateway
6 在项目里创建application.yml
spring:
cloud:
gateway:
routes:
- id: baidu_route
uri: http://www.baidu.com
predicates:
- Query=url,baidu
- id: test_route
uri: http://www.qq.com
predicates:
- Query=url,qq
测试 localhost:8080?url=baidu # 跳到百度页面
测试 localhost:8080?url=baidu # 跳到qq页面
前后端对比
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9C85pEzs-1651150085140)(https://cdn.jsdelivr.net/gh/Jonny-Chi/picgo_imgs/BLog/202204032257179.png)]
ECMAScript6.0(以下简称ES6,ECMAScript是一种由Ecma国际通过ECMA-262标准化的脚本),
是JavaScript语言的下一代标准,2015年6月正式发布,从ES6开始的版本号采用年号,如ES2015,就是ES6。ES2016就是ES7。
ECMAScript是规范,JS的规范的具体实现。
var在{}之外也起作用
let在{}之外不起作用
var多次声明同一变量不会报错,let多次声明会报错,let变量只能声明一次。
var 会变量提升(打印和定义可以顺序反)。let 不存在变量提升(顺序不能反)
## var变量提升实际就是,var的变量在为定义之前可以输出,不报错,只是说未定义的变量未赋值(undefined)
## let变量如果不赋值会报错:【ReferenceError: y is not defined】
const声明之后不允许改变
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<script>
// var 声明的变量往往会越域
// let 声明的变量有严格局部作用域
// {
// var a = 1;
// let b = 2;
// }
// console.log(a);// 1
// console.log(b);// ReferenceError: b is not defined
// var 可以声明多次
// let 只能声明一次
// var m = 1 ;
// var m = 2 ;
// let n = 3 ;
// // let n = 4 ;
// console.log(m) // 2
// console.log(n) // Identifier 'n' has already been declared
// // var 会变量提升
// // let 不存在变量提升
// console.log(x); // undefined
// var x = 10;
// console.log(y); //ReferenceError: y is not defined
// let y = 20;
// 1. 声明之后不允许改变
// 2. 一但声明必须初始化,否则会报错
const a = 1;
a = 3; //Uncaught TypeError: Assignment to constant variable.
script>
body>
html>
支持let arr = [1,2,3]; let [a,b,c] = arr;这种语法
支持对象解析:const { name: abc, age, language } = person; 冒号代表改名
字符串函数
支持一个字符串为多行
占位符功能 ${}
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<script>
// let arr = [1,2,3];
// let a = arr[0];
// let b = arr[1];
// let c = arr[2];
// //以前我们想获取其中的值,只能通过角标。ES6 可以这样:
// const [x,y,z] = arr;// x,y,z 将与 arr 中的每个位置对应来取值 // 然后打印
// console.log(a,b,c);
// console.log(x,y,z);
const person = {
name: "jack",
age: 21,
language: ['java ', 'js ', 'css ']
}
// 解构表达式获取值,将 person 里面每一个属性和左边对应赋值
// const { name, age, language } = person;
// 等价于下面
// const name = person.name;
// const age = person.age;
// const language = person.language;
// 可以分别打印
// console.log(name);
// console.log(age);
// console.log(language);
//扩展:如果想要将 name 的值赋值给其他变量,可以如下,nn 是新的变量名
const { name: nn, age, language } = person;
console.log(nn);
console.log(age);
console.log(language);
//ES6 为字符串扩展了几个新的 API:
// - `includes()` :返回布尔值,表示是否找到了参数字符串。
// - `startsWith()` :返回布尔值,表示参数字符串是否在原字符串的头部。
// - `endsWith()` :返回布尔值,表示参数字符串是否在原字符串的尾部。
let str = "hello.vue";
console.log(str.startsWith("hello"));//true
console.log(str.endsWith(".vue"));//true
console.log(str.includes("e"));//true
console.log(str.includes("hello"));//true
// 1、多行字符串
//模板字符串相当于加强版的字符串,用反引号 `, 除了作为普通字符串,还可以用来定义多行 字符串,还可以在字符串中加入变量和表达式。
let ss = `
hello world
`
console.log(ss);
// 2、字符串插入变量和表达式。变量名写在 ${} 中,${} 中可以放 入 JavaScript 表达式。
// let name = "张三";
// let age = 18;
let info = `我是${nn},今年${age+3}了`;
console.log(info);
// 3、字符串中调用函数
function fun() {
return "这是一个函数"
}
let sss = `O(∩_∩)O 哈哈~,${fun()}`;
console.log(sss); // O(∩_∩)O 哈哈~,这是一个函数
script>
body>
html>
原来想要函数默认值得这么写b = b || 1; 现在可以直接写了function add2(a, b = 1) {
函数不定参数function fun(…values) {
支持箭头函数(lambda表达式),还支持使用{}结构传入对象的成员
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<script>
//1)、函数参数默认值
//在 ES6 以前,我们无法给一个函数参数设置默认值,只能采用变通写法:
function add(a, b) {
// 判断 b 是否为空,为空就给默认值 1
b = b || 1;
return a + b;
}
// 传一个参数
console.log(add(10));
//现在可以这么写:直接给参数写上默认值,没传就会自动使用默认值
function add2(a, b = 1) {
return a + b;
}
// 传一个参数
console.log(add2(10));
// 2)、不定参数
//不定参数用来表示不确定参数个数,形如,...变量名, 由...加上一个具名参数标识符组成。 具名参数只能放在参数列表的最后,并且有且只有一个不定参数
function fun(...values) {
console.log(values.length)
}
fun(1, 2) //2
fun(1, 2, 3, 4) //4
//3)、箭头函数
// ES6 中定义函数的简写方式
//以前声明一个方法
// var print = function (obj) {
// console.log(obj);
// }
// 可以简写为:
var print = obj => console.log(obj);
// 测试调用
print("hello word!!");
// //多个参数:
// 两个参数的情况:
var sum = function (a, b) {
return a + b;
}
// 简写为:
//当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。
var sum2 = (a, b) => a + b;
//测试调用
console.log(sum2(10, 10));//20
// 代码不止一行,可以用`{}`括起来
var sum3 = (a, b) => {
c = a + b;
return c;
};
//测试调用
console.log(sum3(10, 20));//30
//4)、实战:箭头函数结合解构表达式
//需求,声明一个对象,hello 方法需要对象的个别属性
//以前的方式:
const person = {
name: "jack",
age: 21,
language: ['java ', 'js ', 'css ']
}
function hello(person) {
console.log("hello," + person.name)
}
hello(person);
var hello2 = (param) => console.log("hello ! "+param.name);
hello2(person);
var hello3 = ({name}) => console.log("hello ! "+name);
hello3(person);
script>
body>
html>
可以获取map的键值对等Object.keys()、values、entries
Object.assgn(target,source1,source2) 合并
const person2 = { age, name } //声明对象简写
…代表取出该对象所有属性拷贝到当前对象。let someone = { …p1 }
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<script>
//数组中新增了map和reduce方法。
//map():接收一个函数,将原数组中的所有元素用这个函数处理后放入新数组返回。
let arr = ['1', '20', '-5', '3'];
// arr = arr.map((item)=>{
// return item*2
// });
arr = arr.map(item=> item*2);
console.log(arr);
//reduce() 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,
//[2, 40, -10, 6]
//arr.reduce(callback,[initialValue])
/**
1、previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
2、currentValue (数组中当前被处理的元素)
3、index (当前元素在数组中的索引)
4、array (调用 reduce 的数组)*/
let result = arr.reduce((a,b)=>{
console.log("上一次处理后:"+a);
console.log("当前正在处理:"+b);
return a + b;
},100);
console.log(result)
script>
body>
html>
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<script>
//数组中新增了map和reduce方法。
//map():接收一个函数,将原数组中的所有元素用这个函数处理后放入新数组返回。
let arr = ['1', '20', '-5', '3'];
// arr = arr.map((item)=>{
// return item*2
// });
arr = arr.map(item=> item*2);
console.log(arr);
//reduce() 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,
//[2, 40, -10, 6]
//arr.reduce(callback,[initialValue])
/**
1、previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
2、currentValue (数组中当前被处理的元素)
3、index (当前元素在数组中的索引)
4、array (调用 reduce 的数组)*/
let result = arr.reduce((a,b)=>{
console.log("上一次处理后:"+a);
console.log("当前正在处理:"+b);
return a + b;
},100);
console.log(result)
script>
body>
html>
以前嵌套ajax的时候很繁琐。
解决方案:
把Ajax封装到Promise中,赋值给let p
在Ajax中成功使用resolve(data),交给then处理,
失败使用reject(err),交给catch处理p.then().catch()
corse_score_10.json //得分
{
"id": 100,
"score": 90
}
user.json //用户
{
"id": 1,
"name": "zhangsan",
"password": "123456"
}
user_corse_1.json //课程
{
"id": 10,
"name": "chinese"
}
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js">script>
head>
<body>
<script>
//1、查出当前用户信息
//2、按照当前用户的id查出他的课程
//3、按照当前课程id查出分数
// $.ajax({
// url: "mock/user.json",
// success(data) {
// console.log("查询用户:", data);
// $.ajax({
// url: `mock/user_corse_${data.id}.json`,
// success(data) {
// console.log("查询到课程:", data);
// $.ajax({
// url: `mock/corse_score_${data.id}.json`,
// success(data) {
// console.log("查询到分数:", data);
// },
// error(error) {
// console.log("出现异常了:" + error);
// }
// });
// },
// error(error) {
// console.log("出现异常了:" + error);
// }
// });
// },
// error(error) {
// console.log("出现异常了:" + error);
// }
// });
//1、Promise可以封装异步操作
// let p = new Promise((resolve, reject) => { //传入成功解析,失败拒绝
// //1、异步操作
// $.ajax({
// url: "mock/user.json",
// success: function (data) {
// console.log("查询用户成功:", data)
// resolve(data);
// },
// error: function (err) {
// reject(err);
// }
// });
// });
// p.then((obj) => { //成功以后做什么
// return new Promise((resolve, reject) => {
// $.ajax({
// url: `mock/user_corse_${obj.id}.json`,
// success: function (data) {
// console.log("查询用户课程成功:", data)
// resolve(data);
// },
// error: function (err) {
// reject(err)
// }
// });
// })
// }).then((data) => { //成功以后干什么
// console.log("上一步的结果", data)
// $.ajax({
// url: `mock/corse_score_${data.id}.json`,
// success: function (data) {
// console.log("查询课程得分成功:", data)
// },
// error: function (err) {
// }
// });
// })
function get(url, data) { //自己定义一个方法整合一下
return new Promise((resolve, reject) => {
$.ajax({
url: url,
data: data,
success: function (data) {
resolve(data);
},
error: function (err) {
reject(err)
}
})
});
}
get("mock/user.json")
.then((data) => {
console.log("用户查询成功~~~:", data)
return get(`mock/user_corse_${data.id}.json`);
})
.then((data) => {
console.log("课程查询成功~~~:", data)
return get(`mock/corse_score_${data.id}.json`);
})
.then((data)=>{
console.log("课程成绩查询成功~~~:", data)
})
.catch((err)=>{ //失败的话catch
console.log("出现异常",err)
});
script>
body>
html>
模块化就是把代码进行拆分,方便重复利用。类似于java中的导包,
而JS换了个概念,是导模块。
模块功能主要有两个命令构成 export 和import
export用于规定模块的对外接口
import用于导入其他模块提供的功能
user.js
var name = "jack"
var age = 21
function add(a,b){
return a + b;
}
export {name,age,add}
hello.js
// export const util = {
//这种方式导入在import abc from "./hello.js"不可以用自定义的abc变量名,需要固定使用定义的util
// sum(a, b) {
// return a + b;
// }
// }
export default {
sum(a, b) {
return a + b;
}
}
// export {util}
//`export`不仅可以导出对象,一切JS变量都可以导出。比如:基本类型变量、函数、数组、对象。
main.js
import abc from "./hello.js"
import {name,add} from "./user.js"
abc.sum(1,2);
console.log(name);
add(1,3);
MVVM思想
M:model 包括数据和一些基本操作
V:view 视图,页面渲染结果
VM:View-model,模型与视图间的双向操作(无需开发人员干涉)
视图和数据通过VM绑定起来,model里有变化会自动地通过Directives填写到视view中,
视图表单中添加了内容也会自动地通过DOM Listeners保存到模型中。
官网:https://cn.vuejs.org/
参考:https://cn.vuejs.org/v2/guide/
Git 地址:https://github.com/vuejs
Vue2.x初始化
当前文件夹下的终端:
- npm init -y
- npm install vue
Vue3.x初始化
# npm 6.x
$ npm init vite@latest <project-name> --template vue
# npm 7+,需要加上额外的双短横线
$ npm init vite@latest <project-name> -- --template vue
$ cd <project-name>
$ npm install
$ npm run dev
Vue2.x写法
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<div id="app">
<input type="text" v-model="num">
v-model实现双向绑定
<button v-on:click="num++">点赞button>
v-on:click绑定事件,实现自增
<button v-on:click="cancel">取消button>
回到自定义的方法
<h1> {{name}} ,非常帅,有{{num}}个人为他点赞{{hello()}}h1>
div>
<script src="./node_modules/vue/dist/vue.js">script>
<script>
//1、vue声明式渲染
let vm = new Vue({ //生成vue对象
el: "#app",//绑定元素 div id="app"
data: { //封装数据
name: "张三", // 也可以使用{} //表单中可以取出
num: 1
},
methods:{ //封装方法
cancel(){
this.num -- ;
},
hello(){
return "1"
}
}
});
还可以在html控制台vm.name
//2、双向绑定,模型变化,视图变化。反之亦然。
//3、事件处理
//v-xx:指令
//1、创建vue实例,关联页面的模板,将自己的数据(data)渲染到关联的模板,响应式的
//2、指令来简化对dom的一些操作。
//3、声明方法来做更复杂的操作。methods里面可以封装方法。
script>
body>
html>
Vue3.x写法
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<input type="text" v-model="num">
v-model实现双向绑定
<button v-on:click="num++">点赞button>
v-on:click绑定事件,实现自增
<button v-on:click="cancel">取消button>
回到自定义的方法
<h1> {{name}} ,非常帅,有{{num}}个人为他点赞h1>
div>
<script src="./node_modules/vue/dist/vue.global.js">script>
<script>
// const Counter = {
// data() {
// return {
// counter: 0
// }
// }
// }
// Vue.createApp(Counter).mount('#counter')
let app = {
data() { //封装数据
return {
name: "张三", // 也可以使用{} //表单中可以取出
num: 1
}
},
methods:{ //封装方法
cancel(){
this.num -- ;
},
hello(){
return "1"
}
}
}
Vue.createApp(app).mount('#app');//绑定元素 div id="app"
script>
body>
html>
v-text :将数据输出到元素内部,如果输出的数据有 HTML 代码,会作为普通文本输出
v-html :将数据输出到元素内部,如果输出的数据有 HTML 代码,会被渲染
Vue2.x写法
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<div id="app">
{{msg}} {{1+1}} {{hello()}}<br/>
用v-html取内容
<span v-html="msg">span>
<br/>
原样显示
<span v-text="msg">span>
div>
<script src="../node_modules/vue/dist/vue.js">script>
<script>
new Vue({
el:"#app",
data:{
msg:"Hello
",
link:"http://www.baidu.com"
},
methods:{
hello(){
return "World"
}
}
})
script>
body>
html>
Vue3.x写法
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<script src="../node_modules/vue/dist/vue.global.js">script>
<div id="app">
{{msg}} <br/>
{{1+1}} <br/>
{{hello()}}<br/>
<span v-html="msg">span><br/>
<span v-text="msg">span>
<br/>
div>
<script>
const app = {
data() {
return {
msg:"Hello
",
link:"http://www.baidu.com"
}
},methods: {
hello(){
return "World"
}
}
}
Vue.createApp(app).mount('#app');
script>
body>
html>
存在区别:在2.x版本的时候直接用{{}} 取值的时候会直接将插值表达式展示出来,而Vue3.x不会直接展示插值表达式
花括号:只能写在标签体内,不能用在标签内。用v-bind解决
{{}}必须有返回值
html 属性不能使用双大括号形式绑定,我们可以使用 v-bind 指令给 HTML 标签属性绑定值;
而且在将
v-bind
用于class
和style
时,Vue.js 做了专门的增强。
v-bind :style
的对象语法十分直观,看着非常像 CSS ,但其实是一个 JavaScript 对象。style 属性名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case ,这种方式记得用单引号括起 来) 来命名。例如:font-size–>fontSize
Vue2.x写法
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<div id="app">
<a v-bind:href="link">gogogoa>
<span v-bind:class="{active:isActive,'text-danger':hasError}"
:style="{color: color1,fontSize: size}">你好span>
div>
<script src="../node_modules/vue/dist/vue.js">script>
<script>
let vm = new Vue({
el:"#app",
data:{
link: "http://www.baidu.com",
isActive:true,
hasError:true,
color1:'red',
size:'36px'
}
})
script>
body>
html>
Vue3.x写法
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<script src="../node_modules/vue/dist/vue.global.js">script>
<div id="app">
<a v-bind:href="link">gogogoa>
<span :class="{'active':isActive,'text-danger':hasError}"
v-bind:style="{'color':color1,'font-size':size}" >你好span>
div>
<script>
const app = {
data() {
return {
link:"http://www.baidu.com",
isActive:true,
hasError:false,
color1:'red',
size:'100px'
}
},methods: {
}
}
Vue.createApp(app).mount('#app');
script>
body>
html>
v-bind在标签内可以不写,直接用
:
代替即可
刚才的 v-text、v-html、v-bind 可以看做是单向绑定,数据影响了视图渲染,但是反过来就不 行。接下来学习的 v-model 是双向绑定,视图(View )和模型(Model)之间会互相影响。
既然是双向绑定,一定是在视图中可以修改数据,这样就限定了视图的元素类型。 目前 v-model 的可使用元素有:
- input
- select
- textarea
- checkbox
- radio
- components (Vue 中的自定义组件)
基本上除了最后一项,其它都是表单的输入项。
Vue2.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<div id="app">
精通的语言:
<input type="checkbox" v-model="language" value="Java"> java<br/>
<input type="checkbox" v-model="language" value="PHP"> PHP<br/>
<input type="checkbox" v-model="language" value="Python"> Python<br/>
选中了 {{language.join(",")}}
div>
<script src="../node_modules/vue/dist/vue.js">script>
<script>
let vm = new Vue({
el:"#app",
data:{
language: []
}
})
script>
body>
html>
Vue3.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<script src="../node_modules/vue/dist/vue.global.js">script>
<div id="app">
精通的语言:
<input type="checkbox" v-model="language" value="Java"> java<br/>
<input type="checkbox" v-model="language" value="PHP"> PHP<br/>
<input type="checkbox" v-model="language" value="Python"> Python<br/>
选中了 {{language.join(",")}}
div>
<script>
const app = {
data() {
return {
language:[]
}
},methods: {
}
}
Vue.createApp(app).mount('#app');
script>
body>
html>
基本用法
v-on 指令用于给页面元素绑定事件。
语法:
v-on:事件名
="js 片段或函数名"
另外,事件绑定可以简写,例如
v-on:click='add'
简写为@click='add'
事件修饰符
在事件处理程序中调用
event.preventDefault()
或event.stopPropagation()
是非常常见的 需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑, 而不是去处理 DOM 事件细节。为了解决这个问题,Vue.js 为
v-on
提供了事件修饰符。修饰符是由点开头的指令后缀来 表示的。
.stop
:阻止事件冒泡到父元素.prevent
:阻止默认事件发生.capture
:使用事件捕获模式.self
:只有元素自身触发事件才执行。 (冒泡或捕获的都不执行).once
:只执行一次按键修饰符
在监听键盘事件时,我们经常需要检查常见的键值。Vue 允许为
v-on
在监听键盘事件时添 加按键修饰符:<input v-on:keyup.13="submit">
记住所有的
keyCode
比较困难,所以 Vue 为最常用的按键提供了别名:< !-- 同上 --> <input v-on:keyup.enter="submit"> < !-- 缩写语法 --> <input @keyup.enter="submit">
全部的按键别名:
.enter
.tab
.delete` (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
组合按键:
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
.ctrl
.alt
.shift
< !-- Alt + C --> <input @keyup.alt.67="clear"> < !-- Ctrl + Click --> <div @click.ctrl="doSomething">Do somethingdiv>
Vue2.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<div id="app">
<button v-on:click="num++">点赞button>
<button @click="cancel">取消button>
<h1>有{{num}}个赞h1>
<div style="border: 1px solid red;padding: 20px;" v-on:click.once="hello">
大div
<div style="border: 1px solid blue;padding: 20px;" @click.stop="hello">
小div <br />
<a href="http://www.baidu.com" @click.prevent.stop="hello">去百度a>
div>
div>
<input type="text" v-model="num" v-on:keyup.up="num+=2" @keyup.down="num-=2" @click.ctrl="num=10"><br />
提示:
div>
<script src="../node_modules/vue/dist/vue.js">script>
<script>
new Vue({
el:"#app",
data:{
num: 1
},
methods:{
cancel(){
this.num--;
},
hello(){
alert("点击了")
}
}
})
script>
body>
html>
Vue3.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<script src="../node_modules/vue/dist/vue.global.js">script>
<div id="app">
<button v-on:click="num++">点赞button>
<button @click="cancel">取消button>
<h1>有{{num}}个赞h1>
<div style="border: 1px solid red;padding: 20px;" v-on:click.once="hello">
大div
<div style="border: 1px solid blue;padding: 20px;" @click.stop="hello">
小div <br />
<a href="http://www.baidu.com" @click.prevent.stop="hello">去百度a>
div>
div>
<input type="text" v-model="num" v-on:keyup.up="num+=2" @keyup.down="num-=2" @click.alt="num=10"><br />
提示:
div>
<script>
const app = {
data() {
return {
num:1
}
},methods: {
cancel(){
this.num--;
},
hello(){
alert("点击了")
}
}
}
Vue.createApp(app).mount('#app');
script>
body>
html>
遍历数据渲染页面是非常常用的需求,Vue 中通过 v-for 指令来实现
1. 遍历数组
语法:
v-for="item in items
- items:要遍历的数组,需要在 vue 的 data 中定义好。
- item:迭代得到的当前正在遍历的元素
2.数组角标
在遍历的过程中,如果我们需要知道数组角标,可以指定第二个参数:
语法:
v-for="(item,index) in items"
- items:要迭代的数组
- item:迭代得到的数组元素别名
- index:迭代到的当前元素索引,从 0 开始。
3.遍历对象
v-for 除了可以迭代数组,也可以迭代对象。语法基本类似
语法:
- v-for=“value in object” // 1 个参数时,得到的是对象的属性值
- v-for=“(value,key) in object” //2 个参数时,第一个是属性值,第二个是属性名
- v-for=“(value,key,index) in object” //3 个参数时,第三个是索引,从 0 开始
遍历的时候都加上:key来区分不同数据,提高vue渲染效率
Vue2.x与Vue3.x在v-for和v-if上的区别
vue2.x
在同一个元素上同时使用v-if
和v-for
,v-for
会优先使用;vue3.x
v-if
总是优先于v-for
生效如果Vue3.x需要在遍历的时候使用v-if怎么写?
{{item.text}}```
Vue2.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<div id="app">
<ul>
<li v-for="(user,index) in users" :key="user.name" v-if="user.gender == '女'">
当前索引:{{index}} ==> {{user.name}} ==> {{user.gender}} ==>{{user.age}} <br>
对象信息:
<span v-for="(v,k,i) in user">{{k}}=={{v}}=={{i}};span>
li>
ul>
<ul>
<li v-for="(num,index) in nums" :key="index">li>
ul>
div>
<script src="../node_modules/vue/dist/vue.js">script>
<script>
let app = new Vue({
el: "#app",
data: {
users: [{ name: '柳岩', gender: '女', age: 21 },
{ name: '张三', gender: '男', age: 18 },
{ name: '范冰冰', gender: '女', age: 24 },
{ name: '刘亦菲', gender: '女', age: 18 },
{ name: '古力娜扎', gender: '女', age: 25 }],
nums: [1,2,3,4,4]
},
})
script>
body>
html>
Vue3.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<script src="../node_modules/vue/dist/vue.global.js">script>
<div id="app">
<ul>
<template v-for="(user,index) in users">
<li :key="user.name" v-if="user.gender == '女'">
当前索引:{{index}} ==> {{user.name}} ==> {{user.gender}} ==>{{user.age}} <br>
对象信息:
<span v-for="(v,k,i) in user">{{k}}=={{v}}=={{i}};span>
li>
template>
ul>
<ul>
<li v-for="(num,index) in nums" :key="index">li>
ul>
div>
<script>
const app = {
data() {
return {
users: [
{ name: '柳岩', gender: '女', age: 21 },
{ name: '张三', gender: '男', age: 18 },
{ name: '范冰冰', gender: '女', age: 24 },
{ name: '刘亦菲', gender: '女', age: 18 },
{ name: '古力娜扎', gender: '女', age: 25 }
],
nums: [1, 2, 3, 4, 4]
}
}
}
Vue.createApp(app).mount('#app');
script>
body>
html>
v-if ,顾名思义,条件判断。当得到结果为true 时,所在的元素才会被渲染
v-show ,当得到结果为true 时,所在的元素才会被显示。
- 「注意,
v-show
是改变元素的display属性而v-if
的结果为false是直接不显示整个元素」语法:
v-if="布尔表达式", v-show="布尔表达式"
Vue2.x与Vue3.x在v-for和v-if上的区别
vue2.x
在同一个元素上同时使用v-if
和v-for
,v-for
会优先使用;vue3.x
v-if
总是优先于v-for
生效
Vue2.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<div id="app">
<button v-on:click="show = !show">点我呀button>
<h1 v-if="show">if=看到我....h1>
<h1 v-show="show">show=看到我h1>
div>
<script src="../node_modules/vue/dist/vue.js">script>
<script>
let app = new Vue({
el: "#app",
data: {
show: true
}
})
script>
body>
html>
Vue3.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<script src="../node_modules/vue/dist/vue.global.js">script>
<div id="app">
<button v-on:click="show = !show">点我呀button>
<h1 v-if="show">if=看到我....h1>
<h1 v-show="show">show=看到我h1>
div>
<script>
const app = {
data() {
return {
show:true
}
}
}
Vue.createApp(app).mount('#app');
script>
body>
html>
v-else 元素必须紧跟在带
v-if
或者v-else-if
的元素的后面,否则它将不会被识别。
Vue2.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<div id="app">
<button v-on:click="random=Math.random()">点我呀button>
<span>{{random}}span>
<h1 v-if="random>=0.75">
看到我啦?! >= 0.75
h1>
<h1 v-else-if="random>=0.5">
看到我啦?! >= 0.5
h1>
<h1 v-else-if="random>=0.2">
看到我啦?! >= 0.2
h1>
<h1 v-else>
看到我啦?! < 0.2
h1>
div>
<script src="../node_modules/vue/dist/vue.js">script>
<script>
let app = new Vue({
el: "#app",
data: { random: 1 }
})
script>
body>
html>
Vue3.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<script src="../node_modules/vue/dist/vue.global.js">script>
<div id="app">
<button v-on:click="random=Math.random()">点我呀button>
<span>{{random}}span>
<h1 v-if="random>=0.75">
看到我啦?! >= 0.75
h1>
<h1 v-else-if="random>=0.5">
看到我啦?! >= 0.5
h1>
<h1 v-else-if="random>=0.2">
看到我啦?! >= 0.2
h1>
<h1 v-else>
看到我啦?! < 0.2
h1>
div>
<script>
const app = {
data() {
return {
random:1
}
}
}
Vue.createApp(app).mount('#app');
script>
body>
html>
计算属性(computed)
某些结果是基于之前数据实时计算出来的,我们可以利用计算属性来完成
效果:只要依赖的属性发生变化,就会重新计算这个属性
侦听(watch)
watch 可以让我们监控一个值的变化。从而做出相应的反应。
Vue2.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<div id="app">
<ul>
<li>西游记; 价格:{{xyjPrice}},数量:<input type="number" v-model="xyjNum"> li>
<li>水浒传; 价格:{{shzPrice}},数量:<input type="number" v-model="shzNum"> li>
<li>总价:{{totalPrice}}li>
{{msg}}
ul>
div>
<script src="../node_modules/vue/dist/vue.js">script>
<script>
//watch可以让我们监控一个值的变化。从而做出相应的反应。
new Vue({
el: "#app",
data: {
xyjPrice: 99.98,
shzPrice: 98.00,
xyjNum: 1,
shzNum: 1,
msg: ""
},
computed: {
totalPrice(){
return this.xyjPrice*this.xyjNum + this.shzPrice*this.shzNum
}
},
watch: {
xyjNum(newVal,oldVal){
if(newVal>=3){
this.msg = "库存超出限制";
this.xyjNum = 3
}else{
this.msg = "";
}
}
},
})
script>
body>
html>
Vue3.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<script src="../node_modules/vue/dist/vue.global.js">script>
<div id="app">
<ul>
<li>西游记; 价格:{{xyjPrice}},数量:<input type="number" v-model="xyjNum"> li>
<li>水浒传; 价格:{{shzPrice}},数量:<input type="number" v-model="shzNum"> li>
<li>总价:{{totalPrice}}li>
{{msg}}
ul>
div>
<script>
const app = {
data() {
return {
xyjPrice: 99.98,
shzPrice: 98.00,
xyjNum: 1,
shzNum: 1,
msg: ""
}
},
computed: {
totalPrice(){
return this.xyjPrice*this.xyjNum + this.shzPrice*this.shzNum
}
},
watch: {
xyjNum(newVal,oldVal){
if(newVal>=3){
this.msg = "库存超出限制";
this.xyjNum = 3
}else{
this.msg = "";
}
}
},
}
Vue.createApp(app).mount('#app');
script>
body>
html>
过滤器(filters)
过滤器不改变真正的
data
,而只是改变渲染的结果,并返回过滤后的版本。在很多不同的 情况下,过滤器都是有用的,比如尽可能保持 API 响应的干净,并在前端处理数据的格式。
- 针对于Vue2.x
局部过滤器
注册在当前 vue 实例中,只有当前实例能用
全局过滤器
过滤器常用来处理文本格式化的操作。过滤器可以用在两个地方:
- 双花括号插值和 v-bind 表达式
- 针对于Vue3.x
2020年9月份,vue3出来了。增加了很多新功能,但是也删掉了一些功能。比如删掉了vue2中的过滤器filter功能。与此同时,官方建议:
用方法调用或计算属性替换过滤器。
下面Vue3.x具体案例有写实现方式
- 注意事项
- // 计算属性要return一个函数接收参数
Vue2.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<div id="app">
<ul>
<li v-for="user in userList">
{{user.id}} ==> {{user.name}} ==> {{user.gender == 1?"男":"女"}} ==>
{{user.gender | genderFilter}} ==> {{user.gender | gFilter}}
li>
ul>
div>
<script src="../node_modules/vue/dist/vue.js">script>
<script>
Vue.filter("gFilter", function (val) { //全局过滤器
if (val == 1) {
return "男~~~";
} else {
return "女~~~";
}
})
let vm = new Vue({
el: "#app",
data: {
userList: [
{ id: 1, name: 'jacky', gender: 1 },
{ id: 2, name: 'peter', gender: 0 }
]
},
filters: {
//filters 定义局部过滤器,只可以在当前vue实例中使用
genderFilter(val) {
if (val == 1) {
return "男";
} else {
return "女";
}
}
}
})
script>
body>
html>
Vue3.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<script src="../node_modules/vue/dist/vue.global.js">script>
<div id="app">
<ul>
<li v-for="user in userList">
{{user.id}} ==> {{user.name}} ==> {{user.gender == 1?"男":"女"}} <br />
==> 局部过滤器 :
{{user.gender | genderFilter}} <br />
==> 计算属性过滤器实现 :
{{gFilter(user.gender)}}<br />
==> 方法过滤器实现 :
{{gFilterMethod(user.gender)}}
li>
ul>
div>
<script>
// Vue.filter("gFilter", function (val) { //全局过滤器Vue3.x已取消
// if (val == 1) {
// return "男~~~";
// } else {
// return "女~~~";
// }
// });
const app = {
// export default {
data() {
return {
userList: [
{ id: 1, name: 'jacky', gender: 1 },
{ id: 2, name: 'peter', gender: 0 }
]
}
},
filters: { //filters 定义局部过滤器,只可以在当前vue实例中使用
genderFilter(val) {
if (val == 1) {
return "男";
} else {
return "女";
}
}
},
computed: { //计算属性
gFilter() {
return function (val) { // 计算属性要return一个函数接收参数
if (val == 1) {
return "男";
} else {
return "女";
}
}
}
},
methods: {
gFilterMethod(val) {
if (val == 1) {
return "男";
} else {
return "女";
}
}
},
}
Vue.createApp(app).mount('#app');
script>
body>
html>
看到了叭,filter过滤器能加工数据,computed计算属性和methods方法也都可以加工数据,这样的话,就重复了…
vue3删除了filter就好比:
员工filter会干的活,员工computed和员工methods也会干,而且比员工filter干得多,干的好。这样的话,老板vue就把filter开除了,filter就被fired了。毕竟多一个员工,多一些用工成本(员工filter哇的一声哭了出来)
在大型应用开发的时候,页面可以划分成很多部分。往往不同的页面,也会有相
同的部分。
例如可能会有相同的头部导航。
但是如果每个页面都自开发,这无疑增加了我们开发的成本。所以我们会把页面
的不同分拆分成立的组件,然后在不同页面就可以共享这些组件,避免重复开发。
在vue里,所有的vue实例都是组件
组件其实也是一个vue实例,因此它在定义时也会接收:data、methods、生命周期函等
不同的是组件不会与页面的元素綁定,否则就无法复用了,因此没有el属性。
但是组件渲染需要html模板,所以增加了template属性,值就是HTML模板
全局组件定义完毕,任何vue实例都可以直接在HTML中通过组件名称来使用组了
data必须是一个函数,不再是一个对象。
1.全局组件:我们通过 Vue 的 component 方法来定义一个全局组件。
2.组件的复用:定义好的组件,可以任意复用多次
3.局部组件:一旦全局注册,就意味着即便以后你不再使用这个组件,它依然会随着 Vue 的加载而加载。 因此,对于一些并不频繁使用的组件,我们会采用局部注册。
- components 就是当前 vue 对象子组件集合。
- 其 key 就是子组件名称
- 其值就是组件对象名
- 效果与刚才的全局注册是类似的,不同的是,这个 counter 组件只能在当前的 Vue 实例 中使用
Vue2.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<div id="app">
<button v-on:click="count++">我被点击了 {{count}} 次button>
<counter>counter>
<counter>counter>
<counter>counter>
<counter>counter>
<counter>counter>
<button-counter>button-counter>
div>
<script src="../node_modules/vue/dist/vue.js">script>
<script>
//1、全局声明注册一个组件
Vue.component("counter", {
template: ``,
data() {
return {
count: 1
}
}
});
//2、局部声明一个组件
const buttonCounter = {
template: ``,
data() {
return {
count: 1
}
}
};
new Vue({
el: "#app",
data: {
count: 1
},
components: {
'button-counter': buttonCounter
}
})
script>
body>
html>
Vue3.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<script src="../node_modules/vue/dist/vue.global.js">script>
<div id="app">
<button v-on:click="count++">我点击了 {{count}} 次button>
<counter>counter>
<counter>counter>
<counter>counter>
<button-counter>button-counter>
div>
<script>
const buttonCounter = {
template: '',
data() {
return {
count: 1
}
}
}
const app = Vue.createApp({ // 局部组件
data() {
return {
count: 1
}
},
components: {
"button-counter": buttonCounter
}
});
//全局申明注册一个组件
app.component("counter", { //全局组件
template: '',
data() {
return {
count: 1
}
}
});
app.mount("#app");
// Vue.createApp(app).mount('#app');
script>
body>
html>
生命周期
每个 Vue 实例在被创建时都要经过一系列的初始化过程 :创建实例,装载模板,渲染模 板等等。Vue 为生命周期中的每个状态都设置了钩子函数(监听函数)。每当 Vue 实例处于 不同的生命周期时,对应的函数就会被触发调用。
生命周期:你不需要立马弄明白所有的东西。钩子函数「Vue2.x」
- beforeCreated:我们在用 Vue 时都要进行实例化,因此,该函数就是在 Vue 实例化时调 用,也可以将他理解为初始化函数比较方便一点,在 Vue1.0 时,这个函数的名字就是 init。
- created :在创建实例之后进行调用。
- beforeMount :页面加载完成,没有渲染。如:此时页面还是{{name}}
- mounted :我们可以将他理解为原生 js 中的 window.οnlοad=function({.,.}),或许大家也在 用 jquery,所以也可以理解为 jquery 中的$(document).ready(function(){ … .}),他的功能就 是 :在 dom 文档渲染完毕之后将要执行的函数 ,该函数在 Vue1.0 版本中名字为 compiled 。 此时页面中的{{name}}已被渲染成张三
- beforeDestroy:该函数将在销毁实例前进行调用
- destroyed :改函数将在销毁实例时进行调用。
- beforeUpdate :组件更新之前。
- updated :组件更新之后。
钩子函数「Vue3.x」
setup() : 开始创建组件之前,在 beforeCreate 和 created 之前执行,创建的是 data 和 method
onBeforeMount() : 组件挂载到节点上之前执行的函数;
onMounted() : 组件挂载完成后执行的函数;
onBeforeUpdate(): 组件更新之前执行的函数;
onUpdated(): 组件更新完成之后执行的函数;
onBeforeUnmount(): 组件卸载之前执行的函数;
onUnmounted(): 组件卸载完成后执行的函数;
onActivated(): 被包含在 中的组件,会多出两个生命周期钩子函数,被激活时执行;
onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行;
onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数。
PS: 使用**** 组件会将数据保留在内存中,比如我们不想每次看到一个页面都重新加载数据,就可以使用**** 组件解决。与 2.x 版本生命周期相对应的组合式 API
-> 使用beforeCreate
setup()
-> 使用created
setup()
beforeMount
->onBeforeMount
mounted
->onMounted
beforeUpdate
->onBeforeUpdate
updated
->onUpdated
beforeDestroy
->onBeforeUnmount
destroyed
->onUnmounted
errorCaptured
->onErrorCaptured
可以发现:
beforeCreate
和created
与setup
几乎是同时进行的,所以可以把写在beforeCreate
和created
这两周期的代码直接写在setup
里即可。- 命名都形如
onXXX
。beforeDestroy
和destroyed
改为onBeforeUnmount
和onUnmounted
,更语义化了。
Vue2.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<div id="app">
<span id="num">{{num}}span>
<button @click="num++">赞!button>
<h2>{{name}},有{{num}}个人点赞h2>
div>
<script src="../node_modules/vue/dist/vue.js">script>
<script>
let app = new Vue({
el: "#app",
data: {
name: "张三",
num: 100
},
methods: {
show() {
return this.name;
},
add() {
this.num++;
}
},
beforeCreate() {
console.log("=========beforeCreate=============");
console.log("数据模型未加载:" + this.name, this.num);
console.log("方法未加载:" + this.show());
console.log("html模板未加载:" + document.getElementById("num"));
},
created: function () {
console.log("=========created=============");
console.log("数据模型已加载:" + this.name, this.num);
console.log("方法已加载:" + this.show());
console.log("html模板已加载:" + document.getElementById("num"));
console.log("html模板未渲染:" + document.getElementById("num").innerText);
},
beforeMount() {
console.log("=========beforeMount=============");
console.log("html模板未渲染:" + document.getElementById("num").innerText);
},
mounted() {
console.log("=========mounted=============");
console.log("html模板已渲染:" + document.getElementById("num").innerText);
},
beforeUpdate() {
console.log("=========beforeUpdate=============");
console.log("数据模型已更新:" + this.num);
console.log("html模板未更新:" + document.getElementById("num").innerText);
},
updated() {
console.log("=========updated=============");
console.log("数据模型已更新:" + this.num);
console.log("html模板已更新:" + document.getElementById("num").innerText);
}
});
script>
body>
html>
Vue3.x
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<script src="../node_modules/vue/dist/vue.global.js">script>
<div id="app">
<span id="num">{{num}}span>
<button @click="num++">赞!button>
<h2>{{name}},有{{num}}个人点赞h2>
div>
<script>
const app = {
data() {
return {
name: "张三",
num: 100
}
}, methods: {
show() {
return this.name;
},
add() {
this.num++;
}
},
setup() {
console.log("=========setup=============");
console.log("数据模型未加载:" + this.name, this.num);
// console.log("方法未加载:" + this.show());
// console.log("html模板未加载:" + document.getElementById("num"));
},
onBeforeMount() {
console.log("=========onBeforeMount=============");
console.log("html模板未渲染:" + document.getElementById("num").innerText);
},
onMounted() {
console.log("=========onMounted=============");
console.log("html模板已渲染:" + document.getElementById("num").innerText);
},
onBeforeUpdate() {
console.log("=========onBeforeUpdate=============");
console.log("数据模型已更新:" + this.num);
console.log("html模板未更新:" + document.getElementById("num").innerText);
},
onUpdated() {
console.log("=========onUpdated=============");
console.log("数据模型已更新:" + this.num);
console.log("html模板已更新:" + document.getElementById("num").innerText);
}
}
Vue.createApp(app).mount('#app');
script>
body>
html>
Vue2.x
全局安装webpack
1 npm install webpack -g
全局安装vue脚手架
2 npm install -g @vue/cli-init
3 初始化vue项目
vue init webpack appname:vue脚手架使用webpack模板初始化一个appname项目
4 启动vue项目
项目的package.json中有scripts,代表我们能运行的命令
npm start = npm run dev: 启动项目
npm run build:将项目打包
Vue3.x
Vite 是一个 web 开发构建工具,由于其原生 ES 模块导入方式,可以实现闪电般的冷服务器启动。
通过在终端中运行以下命令,可以使用 Vite 快速构建 Vue 项目。
使用 npm:
# npm 6.x
$ npm init vite@latest <project-name> --template vue
# npm 7+,需要加上额外的双短横线
$ npm init vite@latest <project-name> -- --template vue
$ cd <project-name>
$ npm install
$ npm run dev
Vue2.x安装element-ui
Vue3.x安装element-ui
Vue2.x案例
推荐使用 npm 的方式安装,它能更好地和 webpack 打包工具配合使用。
vue2.x
npm i element-ui -S
在 main.js 中添加以下内容:
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
Vue3.x案例
# NPM
# Vue2.x
$ npm install element-plus --save
在 main.js 中添加以下内容:
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')