下载链接:
VirtualBox:https://download.virtualbox.org/virtualbox/6.0.12/VirtualBox-6.0.12-133076-Win.exe
Vagrant:https://releases.hashicorp.com/vagrant/2.2.5/vagrant_2.2.5_x86_64.msi
打开cmd后,使用vagrant init centos/7来初始化虚拟机,这样会在C盘下出现一个vagrantfile
然后我们键入vagrant up即可创建虚拟机(从官方拉取镜像)
拉取完后,发现这里会报错,这里用的2.2.19,尝试换成低版本的2.2.5
默认虚拟电脑的位置中不能包含中文路径:
上述解决了虚拟机无法下载的问题,然后出现了虚拟机无法启动的问题,输入如下命令解决
bcdedit /set hypervisorlaunchtype off
还需要保证下面两个功能是开启的:
然后电脑需要重启,看到如下界面表示成功:
(如果本地已经有了虚拟机就不用在这里创建了)
下次启动虚拟机只需要在vagrantfile所在的文件夹下使用命令vagrant ssh即可连接虚拟机,使用vagrant up启动虚拟机,也可以直接在virtualbox来启动虚拟机
virtualbox默认的网络使用方式是端口映射方式,即将虚拟机的端口和windows下的端口进行映射
但是这样显然过去麻烦,每下载一个软件都要进行一次端口映射,所以如果能给虚拟机一个独立的IP地址,就可以通过直接通过IP地址访问虚拟机
使用ipconfig获取VirtualBox的ip
因为掩码是255.255.255.0,所以主机名是最后一个数字,整个virtualbox下的虚拟机都应当在这个子网中
在VagrantFile中修订ip地址,在这个子网中就行,这里设置为192.168.56.10:
config.vm.network "private_network", ip: "192.168.56.10"
vagrant reload重启虚拟机
win10和虚拟机能相互ping同就算成功
什么是docker
docker可以从网上拉取各个软件的镜像,这些镜像配置好了软件的运行环境,docker可以从软件仓库里下载镜像,启动容器后就可以使用,软件和软件之间相互隔离,一个软件出现问题不会影响到其他容器
安装docker
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
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
3 安装DOCKER引擎
sudo yum install docker-ce docker-ce-cli containerd.io
开机自启动
sudo systemctl enable docker
4 启动Docker.
sudo systemctl start docker
5 配置镜像加速
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://chqac97z.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
阿里云容器镜像加速服务:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors
这样容器加载速度就会加快
可以使用docker安装各种各样的软件
https://hub.docker.com/search?q=mysql
默认是最新版本
sudo docker pull mysql
下载指定版本
sudo docker pull mysql:5.7
查看当选下载的所有镜像
sudo docker images
vagrant创建的虚拟机的root用户的密码是vagrant,可以使用su root
来登录root用户
启动mysql容器
1 拉去mysql镜像
sudo docker pull mysql:8.0
2 启动mysql容器
# --name指定容器名字
#-v目录挂载
#-p指定端口映射 将容器的3306端口,映射到服务器的3306端口
#-e设置mysql参数,这里设置密码为root
#-d后台运行
#
sudo docker run
-p 3306:3306 \
--name mysql \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7
3 使用su - root(切换为root,这样就不用每次都sudo来赐予了)
su - root
4 进入mysql容器
docker exec -it 容器名称|容器id /bin/bash
看到一个很长的字符串,表示mysql安装好了,即可从外部进行连接(但是本机没有mysql这个命令所以打不开)
sudo docker run -p 3306:3306 --name mysql \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7
进入docker容器内部
sudo docker exec -it mysql /bin/bash
# -it 交互方式
# mysql 容器名称
# /bin/bash 以控制台进入
容器内部就是一个完整的linux系统,mysql容器也就是一个装有mysql的linux的系统,我们可以通过修改容器中的配置文件来修改mysql的配置,但是每次都要进入容器修改配置有些过于麻烦了,所以我们将容器内部一些我们感兴趣的文件夹和外部的一些文件夹建立映射关系,这样修改配置文件就不需要进入容器修改,在容器外部就能修改,相当于是容器内文件的快捷方式,这就是文件挂载。使用-v来设置文件挂载的映射关系
比如我们想修改mysql的配置文件,我们可以来到:
/mydata/mysql/conf
然后sudo vi my.cnf来编写配置文件
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
init_connect='SET collation_connection = utf8_unicode_ci'
init_connect='SET NAMES utf8'
character-set-server=utf8
collation-server=utf8_unicode_ci
skip-character-set-client-handshake
skip-name-resolve
wq退出后,我们进入容器内部,查看/etc/mysql/,会发现有一个my.cnf,里面是我们刚才编写的配置文件,表示文件挂载发挥了作用
#拉取redis镜像
sudo docker pull redis
#提前创建好文件,原因后面解释
#级联创建文件夹
mkdir -p /mydata/redis/conf
touch /mydata/redis/conf/redis.conf
#运行容器
docker run \
--name redis \
-v /mydata/redis/data:/data \
-v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \
-p 6379:6379 \
-d redis redis-server /etc/redis/redis.conf
#--name redis 设置名称
#-v /mydata/redis/data:/data 数据映射路径
#-v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf 配置文件映射
#-p 6379:6379 端口映射
#-d redis redis-server /etc/redis/redis.conf 设置启动容器时加载的配置文件
注意:容器中路径/etc/redis/redis.conf
下是没有这个文件的,直接执行上述命令会被当成文件夹来映射,所以我们可以在启动前将这个文件创建好,这样就不会被识别成文件夹
测试是否启动成功:
#docker exec -it 容器名称 命令
docker exec -it redis redis-cli
启动成功
但此时redis是没有持久化的,我们可以在配置文件中设置为AOF持久化方式(RDB镜像为基础,在后面加上操作信息,使用这两者还原出redis中的数据)
vi /mydata/redis/conf/redis.conf
#开启AOF持久化
appendonly yes
然后重启redis容器,因为我们刚才设置了redis要读取的配置文件,所以redis启动的时候会加载我们刚才的配置
docker restart redis
我们可以在外部连接redis,表示安装成功
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0wgaTiEc-1656740867539)(D:/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/picture/image-20220612012051212.png)]
在settings.xml中配置
在maven配置文件配置
配置阿里云镜像
<mirrors>
<mirror>
<id>nexus-aliyunid>
<mirrorOf>centralmirrorOf>
<name>Nexus aliyunname>
<url>http://maven.aliyun.com/nexus/content/groups/publicurl>
mirror>
mirrors>
配置 jdk 1.8 编译项目
<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>
在idea上安装两个常用的插件:Lombok,MyBatisx
使用vscode开发前端项目,在vscode上安装如下插件:
vscode
Auto Close Tag
Auto Rename Tag
Chinese
ESlint
HTML CSS Support
HTML Snippets
JavaScript (ES6) code snippets
Live Server
open in brower
Vetur
安装和配置GIT
# 配置用户名
git config --global user.name "lth" //(名字,随意写)
# 配置邮箱
git config --global user.email "[email protected]" // 注册账号时使用的邮箱
# 配置ssh免密登录
ssh-keygen -t rsa -C "[email protected]"
三次回车后生成了密钥,也可以查看密钥
cat ~/.ssh/id_rsa.pub
浏览器登录码云后,个人头像上点设置、然后点ssh公钥、随便填个标题,然后赋值
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC6PFHhz6X00ruMjWGL6cib/prGXMWCvv6UxA0pY0xbvm0jpTX/h3GKmJ6GebIm+rnghOb6twwSdSGM+TPBPu6HAqEoEVgi7By9MB95KdfM2KCOAn8hI1i3LvZviz/qB+DshmDp5rotVg1pLWEeyqYsweWnnUZ9S5nhgegapG4HKLkz77kBug93kExpT1mg3RwuviHdJIbKfKly3ZIFX6c/cLZVi78Dgh6q8CHVc9rU0wCe3Pf/mLwedmmoKbGVmarfDCmd7fvAa5eGVbRJjCflwgsfCcuRb8CNhXGtROu9i+w2/CV7gYVHMRgy/R01Ys211MfQrinsAwKD7FL9Lgu3OQNJF+qZ2lUmH49nacmDADRxZ2gUKXGjX6s5JrcQ4v+HNk5opAgrusRDrVf8VgpSD/WnFFQmf3D/W3sEQI8HKnCguzShynGh8Hke4DuK22jGHX1zkUnNzqF23VJqDwPfg5lP9rIobBX9dDGT8DLX6iO49ciGOUDvnPyStottvrk= 黎明终点x@LAPTOP-P77BTT69
# 测试
ssh -T [email protected]
# 测试成功
Hi unique_perfect! You've successfully authenticated, but GITEE.COM does not provide shell access.
建好之后复制URL
在idea中导入这个项目:
然后输入刚才复制的url
即可帮我们从GITEE上CLONE下来,我们在这个项目目录下开发各个微服务
Spring Initializr的官方连接(idea有时候会犯病)
Spring Initializr
可以在官网创建好项目模板,然后使用idea导入(项目结构->模块)
商品服务,仓储服务,订单服务,优惠卷服务,用户服务
共同:
也就是控制项目的左边一致,所有微服务都是在这个组名下com.lth.gulimall,每个微服务有自己具体的工件名product
com.lth.gulimall
product
小技巧:至少选择一个springboot的依赖和springcloud的依赖,这样spring initializr就会自动帮我们处理两者的版本依赖问题
按照这种方法(使用idea的spring initializr或者在官网下载然后导入),创建好五个微服务项目,我们为了方便管理所有微服务项目的版本,我们可以将外部的文件夹设置为一个聚合工程,添加pom文件,用于聚合所有的小模块
点击圆圈后加载pom文件,然后如果没有显示gulimall,则点击+号将gulimall这个聚合工程添加进来,这样gulimall使用clean,compile等操作后,所有的模块都会进行相同的操作,方便管理
然后我们需要编写.gitignore,这样在提交项目的时候就不会提交垃圾文件到gitee上面
**/xxx
表示忽略任意路径的这个文件,在聚合工程的.gitignore里面添加如下内容,在c提交仓库时,忽略我们不想要的文件
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
**/mvnw
**/mvnw.cmd
**/.mvn
**/target/
**/.gitignore
**/*.iml
**/node_modules
VCS:版本控制
将代码文件添加到VCS后,如果项目文件发生了变化,idea就会提醒我们
然后我们点击提交后,就可以排除掉我们不需要的文件,提交到仓库里的只有src文件夹和pom,仓库就会比较干净
这里不添加任何外键,一方面外键很消耗性能,另一方面会对我们的编码进行限制
PowerDesigner下载:PowerDesigner安装教程(含下载+汉化+破解) - 暗渡陈仓xy - 博客园 (cnblogs.com)
设置redis,mysql容器开机自启动
sudo docker update redis --restart=always
sudo docker update mysql --restart=always
因为每一个微服务都是在不同的服务器上,使用不用的数据库,所以我们需要为每一个微服务创建自己的数据库
数据库的名称都是gulimall_<首字母>ms
字符集选utf8mb4,他能兼容utf8且能解决一些乱码的问题。
gulimall_oms 订单 order
gulimall_pms 商品 product
gulimall_sms 营销 sale
gulimall_ums 用户 user
gulimall_wms 库存 ware
然后在对应的数据库中创建数据库表,运行每个数据库对应的sql脚本
我们从Gitee上下载一个人人开源提供的通用后台管理系统,可以帮我们快速开发一个后台管理系统界面,而避免我们后端开发人员编写大量的前端代码
网址:人人开源 - Gitee.com
可以在这里下载我们需要的开源产品
renren-fast 是后台管理系统的后端
renren-fast-vue 是后台管理系统的前端项目(使用Vue编写)
renren-security 是前后端没有分离,使用模板引擎的后台管理系统
renren-generator 用于快速生成代码
renren-fast-adminite 是用原生的html和css编写的前端界面
我们这里使用renren-fast和renren-fast-vue来编写整个后台管理系统
renren-fast-vue:https://gitee.com/renrenio/renren-fast-vue.git
renren-fast:https://gitee.com/renrenio/renren-fast.git
我们首先用git-clone将这两个项目下载下来
将renren-fast导入我们后端的工程目录(记得删除里面的.git,否则仓库管理会出现问题)
然后为我们的后台管理系统创建一个属于它的数据库
db里面有我们想要的数据库文件,我们使用的是mysql,所以将mysql里的内容复制到navicat中运行即可
这里我们可以看到经典的profile配置文件切换,在application.yml这个主配置文件中设置使用哪个配置文件,目前有三种配置文件:dev(开发),prod(生产),test(测试)
主配置文件种确实设置了当前所使用的配置文件:dev版本,也就是开发版(也可以在启动jar包时动态指定)
目前这三种配置文件都一样,我们在开发的时候使用dev版,项目上线的时候使用prod版,测试的时候使用test版
使用的是com.alibaba.druid.pool.DruidDataSource数据源,可以方便我们对sql的执行情况进行监控
想要运行开源项目,配置文件一般都需要修改如下地方:
1.修改数据库信息,包括连接,用户名,密码,驱动等
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/renren_fast?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: renren
password: 123456
对这些地方进行修改:
修改后:
运行时发现它无法通过编译,检查pom文件的依赖出现问题:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.6version>
<relativePath/>
parent>
加上**,表示确保父工程从仓库种获取(org.springframework.boot),而不是从上一级目录的pom中获取(gulimall)所以如果不加这个它会从gulimall中获取而无法获得spring配置好的版本信息,因而这个标签必须要加上**
加上后重新加载pom文件,即可通过编译
安装node10.16.3 :https://nodejs.org/download/release/v10.16.3/node-v10.16.3-x64.msi
按照node教程:https://www.cnblogs.com/liluxiang/p/9592003.html
安装后记得重启一下编译器
设置镜像仓库:
npm config set registry http://registry.npm.taobao.org/
设置依赖的保存地址:
npm set cache "D:\nodereps\npmcache"
npm set prefix "D:\nodereps\npmglobal"
报错:记得删除不对的环境变量
进行上述修改后,将D:\nodereps\npmglobal
设置为新的环境变量
查看依赖的设置
npm config ls
npm config ls -l #更加详细
项目启动前,需要先下载前端相关的依赖:
在这之前要将node_moudles删除再进行下载
npm install
package.json里面会描述所有需要下载的依赖,下载后的依赖在node_moudles里面
依赖下载完成后,使用npm run dev 来启动前端项目
nodejs的配置文件:~/.npmrc
在里面我们可以重写nodejs的配置
registry=http://registry.npm.taobao.org/
cache=D:\nodereps\npmcache
prefix=D:\nodereps\npmglobal
用的版本是8.11.0
尽量用下面这个版本
python要在3.0以上,这里用的Python 3.10.5
记得用管理员身份打开vscode,路径里尽量不要有中文
安装前可以清理一下缓存npm cache clear --force
其他错误
记得将global里面的npm卸载掉在使用信道npm
关于新谷粒P16的前端项目使用npm install报错的问题,首先确保安装了python3.0以上版本,并配置全局变量
其次大部分错误是报node-sass4.9.0安装失败。
执行以下步骤可以完美解决
首先把项目文件夹下的package.json里面的node-sass4.9.0改成4.9.2(不改可能也没关系,不过我改了,防止踩坑)
然后项目文件夹下打开cmd命令窗口(和Visual Studio Code的终端命令是一样的)
执行:
npm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
执行成功看看有没有报错,如果没报错执行下面命令
npm install ,
没报错就是安装成功,然后使用npm run dev 就ok了
注:这么做得原理就是先单独从淘宝镜像吧nod-sass下载下来,然后再进行编译,因为这句命令好像是不成功的,(npm config set registry http://registry.npm.taobao.org/),默认从github下载,导致报错的
如果之前安装失败的。先清理 缓存
清理缓存:npm rebuild node-sass
npm uninstall node-sass
npm install 报错可以试试 npm config set msvs_version 2019(根据自己安装的VS版本设置)
默认端口是8002,看到这个界面表示成功
账号密码默认都是admin,验证码的来源是renren-fast后台,输入验证码后能进入系统,表示前后端联调成功
下载renren-generator代码生成器,加入到我们的项目当中
下面以product这个项目为例,来生成代码
# mysql
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
#MySQL配置
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.56.10:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: root
数据库的名称是我们要逆向生成的数据库
3.修改generator.properties
#项目组名对应com.lth.gulimall
mainPath=com.lth.gulimall
#具体项目包的全路径名 com.lth.gulimall 和mainPath一样就行,package和moduleName共同组合出包的结构
package=com.lth.gulimall
#具体的模块名product,对应product
moduleName=product
author=litianhang
[email protected]
#表的前缀,同一个数据库的表可以都使用同一个前缀,这里在生成代码的时候可以忽略表前缀
tablePrefix=pms_
4.启动这个springboot项目,打开80端口,可以看到如下界面,表示数据库连接是正确的
这里记得将每页的个数调到最大,否则成功的代码不全
我们可以选择上面的表来生成对应的代码,生成基本的增删改查,生成一个压缩包,将里面的main包替换我们工程目录下的main包
5.选择所有的表,点击生成代码后,会下载一个压缩包,解压后里面有一个main目录,用它替换我们工程的main目录,这样mybatis-plus逆向工程就完成了
生成代码的来源是我们的数据库表结构
生成了代码后,我们发现生成的代码需要很多依赖,而这些依赖都是公共的,我们可以单独写一个模块common,让所有微服务项目引入这个模块,在这个模块中添加一些公共的依赖和工具类,这样可以方便我们统一管理所有的项目
缺少的类,我们根据引入路径找到对应的类,然后放入同级目录即可(尽量保留包名)
所有的微服务项目导入common:
<dependency>
<groupId>com.lth.gulimallgroupId>
<artifactId>commonartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
mybatis-plus:
对应表的名称:
@TableName("tablename")
对应表的主键id:
@TableId
根据生成的代码中的包的引入规则,工具类需要放在common模块下的utils包下,com.lth.gulimall是工程主目录名,common是引入的模块名,安装这种规则设置包的结构,引入后,common和product虽然在物理上是隔离的,但是逻辑上都在主目录com.lth.gulimall下,可以引用其他模块的功能,就像引用自己工程目录下的一样,这也体现maven包管理的优越之处
有一点,我们权限控制使用的是SpringSecurity而不是org.apache.shiro(当然,并不是说它不好)
import org.apache.shiro.authz.annotation.RequiresPermissions;
因而我们可以调整逆向工程的模板,让他不生成RequiresPermissions相关的配置,代码生成的模板在template目录下:
我们打开它的模板,可以看到里面的结构和我们平时写的Controller的结构是一样的,里面的参数都是动态获取的,我们通过修改这个模板(注释掉RequiresPermissions相关的内容),这样在代码生成的时候就不会生成对应的内容
然后生成新的代码,替换原先的main包即可
小结:
开发微服务的步骤:
1.配置mybatis-plus
(1)导入依赖
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.1version>
dependency>
(2)配置数据源
导入数据库驱动,8.0的驱动能适配5.7到8.0*,所以导入8.0系列即可,同时还需要导入ServletAPI相关的依赖,一般tomcat里面有这个依赖,所以我们打包的时候可以将其排除,打包时排除不需要的依赖,可以使用provided
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.11version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>servlet-apiartifactId>
<version>2.5version>
<scope>providedscope>
dependency>
在application.yml中配置数据源信息
spring:
datasource:
username: root
password: root
url: jdbc:mysql://192.168.56.10:3306/gulimall_pms
driver-class-name: com.mysql.jdbc.Driver
(3)配置mybatis-plus
使用MapperScan设置dao的接口所在的位置@MapperScan("com.lth.gulimall.product.dao")
如果使用的是@Mapper注解则不需要MapperScan,如果使用的是@Repository注解则需要MapperScan
@MapperScan("com.lth.gulimall.product.dao")
@SpringBootApplication
public class gulimallProductApplication {
public static void main(String[] args) {
SpringApplication.run(gulimallProductApplication.class, args);
}
}
设置mapper接口对应的xml文件所在的位置
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
global-config:
db-config:
id-type: auto
其中classpath*
,在classpath后面加上了一个*,表示不止扫描自己项目的classpath,还要扫描引入的其他的包的classpath
/mapper/**/*.xml
:表示mapper目录下任意子目录的xml文件
mybatis-plus.global-config.db-config.id-type=auto
:将所有带有@TableId的主键设置为自增主键
可以在test包下进行测试
@SpringBootTest
class ProductApplicationTests {
@Autowired
BrandService brandService;
@Test
void testSave() {
final BrandEntity brandEntity = new BrandEntity();
brandEntity.setName("华为");
brandService.save(brandEntity);
System.out.println("保存成功");
}
@Test
void testUpdate(){
final BrandEntity brandEntity = new BrandEntity();
brandEntity.setBrandId(1L);
brandEntity.setDescript("华为");
brandService.updateById(brandEntity);
}
@Test
void testQuery(){
brandService.list();
}
}
测试成功后,表示product工程生成完成,其他生成其他微服务的代码时,只需要修改模块名,数据库名,数据库配置名,生成后引入common依赖即可
模块和数据库,端口的对应关系:
pms->product->10000
sms->coupon->7000
ums->member->8000
oms->order->9000
wms->ware->11000
url的规则是:模块名+表名+功能名
数据库的驱动名称两者都可以使用,但是注意url的编写规则
旧版
url: jdbc:mysql://192.168.56.10:3306/gulimall_wms
driver-class-name: com.mysql.jdbc.Driver
新版
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.56.10:3306/gulimall_wms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
Mysql的longblob对应Java的Byte[]类型
SpringCloud和SpringCloud Alibaba
SpringCloud Alibaba提供的解决方案
使用方式,在pom文件中导入
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>2021.0.3version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2021.0.1.0version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
使用springcloud版本管理器,这样以后使用springcloud的组件就不用设置它的版本号,由dependencyManagement统一管理,这样也就不会出现版本冲突问题,一个是第一版的springcloud的版本管理器,第二个是springcloud-alibaba的版本管理器
我们把这个版本管理器放入到common里面,对进行全局统一的版本管理,引入依赖时在common中引入即可,springboot貌似不能这么做,虽然可以通过编译但是运行会报错,原因不明
因为所有的微服务都要注册到注册中心里面,所以在公共依赖处引入注册中心Nacos的依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
然后我们需要有一个注册中心,alibaba将注册中心作为一个中间价提供给了我们,需要我们下载
下载地址:Releases · alibaba/nacos · GitHub
下载链接:https://objects.githubusercontent.com/github-production-release-asset-2e65be/137451403/dad3915e-098e-4e72-a4c5-3c6a06f086aa?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20220613%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20220613T084526Z&X-Amz-Expires=300&X-Amz-Signature=ec24943790723ac0cdcc582bb0e87bb99e0cedf34bc617a33d8503407c025070&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=137451403&response-content-disposition=attachment%3B%20filename%3Dnacos-server-2.1.0.zip&response-content-type=application%2Foctet-stream
下载那个压缩包,解压后来到bin目录,点击startup.cmd启动,此时会报错,因为默认是以集群方式启动的,我们这里使用单节点方式启动,打开cmd窗口,使用startup.cmd -m standalone
看到如下界面表示启动成功
注册中心的运行端口为8848
在微服务处配置注册中心的地址:
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
在启动类(或者配置类)加上注解@EnableDiscoveryClient
@SpringBootApplication
@EnableDiscoveryClient
public class CouponApplication {
public static void main(String[] args) {
SpringApplication.run(CouponApplication.class, args);
}
}
我们可以在http://localhost:8848/nacos/来查看可视化界面,如果不再本机需要替换成nacos所在的ip
登录的账号密码都默认是nacos
微服务处配置微服务的名称:
spring.application.name=coupon
安装这种方法,为所有微服务添加注册中心
小结:
看到如下界面表示成功
代码生成里面的R类
所有的返回值都设置为这个类,其实就是将HashMap包装成更好用的一个类,可以进行链式编程
public class R extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
public R() {
put("code", 0);
put("msg", "success");
}
public static R error() {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
}
public static R error(String msg) {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
}
public static R error(int code, String msg) {
R r = new R();
r.put("code", code);
r.put("msg", msg);
return r;
}
public static R ok(String msg) {
R r = new R();
r.put("msg", msg);
return r;
}
public static R ok(Map<String, Object> map) {
R r = new R();
r.putAll(map);
return r;
}
public static R ok() {
return new R();
}
@Override
public R put(String key, Object value) {
super.put(key, value);
return this;
}
}
Feign的使用步骤:
1.引入OpenFeign需要的依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-loadbalancerartifactId>
dependency>
当然,别忘了添加版本管理器:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>2021.0.3version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2021.0.1.0version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
2.编写远程调用的接口
@FeignClient("coupon")
public interface CouponFeignService {
@RequestMapping("coupon/coupon/member/list")
public R memberCoupon();
}
@FeignClient(“coupon”) 指定当前接口调用的哪个服务(通过服务名称)
@RequestMapping(“coupon/coupon/member/list”) 调用的方法的完整URL
public R memberCoupon(); 调用方法的完整签名
将这些远程调用的接口都统一放到一个目录feign下
3.开启Feign远程调用,并设置包扫描路径
在配置类上添加注解:@EnableFeignClients(basePackages = “com.lth.gulimall.member.feign”)
Springboot启动类是一个主配置类,可以直接在这个上面加上注解
@EnableFeignClients(basePackages = "com.lth.gulimall.member.feign")
@SpringBootApplication
@EnableDiscoveryClient
public class MemberApplication {
public static void main(String[] args) {
SpringApplication.run(MemberApplication.class, args);
}
}
4.调用服务
直接将CouponFeignService依赖注入后就可以直接使用,就好像使用本地的服务一样
@RestController
public class TestController {
@Autowired
CouponFeignService couponFeignService;
@RequestMapping("test")
public void test(){
System.out.println(couponFeignService.memberCoupon());
}
}
如果没有添加spring-cloud-starter-loadbalancer,调用时可能会报错:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testController': Unsatisfied dependency expressed through field 'couponFeignService'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.lth.gulimall.member.feign.CouponFeignService': Unexpected exception during bean creation; nested exception is java.lang.IllegalStateException: No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalancer?
可能高版本的OpenFeign没有添加负载均衡的依赖,我们将这个依赖添加上
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-loadbalancerartifactId>
dependency>
测试成功:
配置中心用于动态修改应用的配置,如果不使用配置中心,我们需要修改配置文件的时候,需要将项目重新打包部署运行,即使使用了profiles,也需要重新启动应用,二配置中心可以在不重启应用的情况下,在线修改应用的配置项
1.引入依赖
因为所有微服务都需要配置中心的配置服务,所以将依赖放到common里面,想要bootstrap.properties配置文件生效,需要添加bootstrap的配置配置
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bootstrapartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
2.编写配置中心的位置
编写bootstrap.properties作为我们配置中心的配置项,用于配置nacos作为配置中心的的元数据,配置配置中心的url和注册进配置中心的应用名称
spring.application.name=coupon
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
配置优先级(前面的会覆盖后面的) application.properties>application.yml>bootstrap.properties
3.使用配置项
我们可以使用@Value注解来获取配置项的信息,使用"${}"来获取配置项,如果不加@RefreshScope注解,配置项的信息仍然是从本地配置文件中获取,而加上@RefreshScope注解后,每次使用都会优先从配置中心获取配置项,注入到变量中,如果配置中心没有再使用本地配置文件
@RestController
@RefreshScope
public class TestController {
@Value("${coupon.user.name}")
String name;
@Value("${coupon.user.age}")
Integer age;
@RequestMapping("/test")
public R memberCoupon(){
return Objects.requireNonNull(R.ok().put("name", name)).put("age",age);
}
}
properties中的配置项为:
coupon.user.name=lth
coupon.user.age=20
测试后,得到的信息和配置项一样
4.使用配置中心修改配置项
首先点击这个表示新建配置项
然后我们可以编写需要发布的配置
Data ID:应用的名称.properties
Group :分组
点击发布后即可修改我们的配置项,暂时使用默认分组
5.测试
原来的配置
在配置中心进行修改:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nr3NqOcU-1656740867569)(D:/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/picture/image-20220614120356017.png)]
得到结果
证明了优先会加载配置中心的配置
然后如果修改成如下配置:
得到结果
删除了coupon.user.age这项配置后,应用从配置中心获取不到,于是加载本地的配置
配置中心每次编辑配置后都会覆盖前面的配置,通过配置中心我们可以很方便的在线修改配置
命名空间:用于配置隔离,默认新增的配置都在public命名空间
比如我们可以新增三个命名空间:dev,prod,test
我们可以在这些命名空间下发布对应的配置文件
应用默认使用public命名空间,如果想切换命名空间,可以在bootstrap.properties中配置
每个命名空间都有属于它的id
spring.cloud.nacos.config.namespace=
在这里设置命名空间的uuid即可切换命名空间,不设置使用的就是public命名空间,达到profile类似的效果
需要在bootstrap.properties中设置使用哪个命名空间的id(不能写名字)
然后我们在想要的命名空间下编写配置,需要使用的时候在代码中切换即可
只使用dev/prod/test模式的命名空间,当配置文件较多时,配置文件会比较混乱,我们可以为每个微服务创建一个命名空间,这样每个微服务的配置都是隔离的
配置集:所以配置的集合叫做配置集
默认都是DEFAULT_GROUP,同一个微服务的配置文件,不仅可以在不同的命名空间,还能在不同的配置分组,使用
spring.cloud.nacos.config.group=DEFAULT_GROUP
配置项来选择分组,确定命名空间和配置分组后,微服务应用使用哪个分组就确定了
我们一般安装微服务个数创建命名空间,按照上线环境创建配置分组(dev,prod,test)
配置中心的功能不仅可以帮我们在线修改配置,还可以帮我们将一个很大的配置文件拆分几个小的,功能分明的配置文件,然后在配置文件中指定启动和运行的时候加载注册中心中的哪些配置文件即可
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
data-id和group分别代表下面两个数据,refresh表示动态刷新配置
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6k4zNMmo-1656740867572)(D:/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/picture/image-20220614224512595.png)]
这里只设置了一个配置文件,可以增加下标来设置更多的配置文件
properties配置文件要以properties结尾,yml配置文件以yml结尾
应用启动的时候,除了读取上述配置,还会默认读取<应用名>.properties配置文件(比如coupon.properties)
springboot获取配置的方式都是用于配置中心,并且配置中心的优先级高于本地配置文件
1.从注册中心拉取状态,监控服务状态,外部调用服务时都通过网关来调用,这样即使后台的路由发送了变化,也不用更改前端代码
2.将鉴权,转发,日志收集等操作统一放到网关来完成
Route :路由,请求的URL
Redicat:断言,根据URI判断请求需要路由到哪个服务
Filter:过滤器,对不合法的请求进行过滤或者拦截处理(动态代理)
请求发过来后,先根据URI匹配服务,如果匹配成功则交给能处理这个请求的handler,经过一系列的Filter达到需要使用的服务,得到返回值后再经过一系列Filter后返回给客户端
新建一个项目引入网关所需的依赖和我们在common设置的依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
网关编排的端口为88
我们再common引入了数据库连接依赖需要我们配置数据库连接,但是网关不需要连接数据库,我们可以在启动类排除数据源的自动配置类,这样就不会加载数据库的配置,也就不需要我们设置数据库连接。
因为网关也需要从注册中心拉取和注册服务,所以需要加上注解@EnableDiscoveryClient
@EnableDiscoveryClient
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
并且网关不能和spring-boot-starter-web
依赖共存,我们可以加上配置
spring.main.web-application-type=reactive
让SpringMVC功能不生效即可
假如我们想要实现一个这样的功能:
当uri是hello,并且请求参数url是baidu时,转发路由到www.baidu.com
http://localhost:88/?url=baidu
可以通过在yml中编写路由规则来实现这个功能
spring:
cloud:
gateway:
routes:
- id: test_route
uri: https://www.baidu.com
predicates:
- Query=url,baidu
- id: qq_route
uri: https://www.qq.com
predicates:
- Query=url,qq
yml语法中-
表示集合中的一个元素,这里的routes和predicates都是对象集合,使用-
表示下面要申明集合中的一个对象,后面设置对象的属性值
测试成功:
此时我们也看到url并没有变化,说明使用的是forward而不是redirect
ES6:JavaScript的一套标准
ECMAScript是浏览器脚本语言的规划,JavaScrpt是规范的实现
1.let 有严格的作用域 var申明会越域
2.let在作用域只能申明一次 var可以申明多次
3.let不会变量提升 var会变量提升
变量提升,在定义前使用变量不会报错,而是以undefined的值存在
console.log(x);
var x=3;
而用let申明则会报错
console.log(x);
let x=3;
总结:我们一般直接使用let申明变量,而一般不用var(特性比较反人类),const作用和let一样,只是const用于申明常量
let arr=[1,2,3];
let a=arr[0];
let b=arr[1];
let c=arr[2];
console.log(a,b,c);
不过也有更方便的赋值方法:解构表达式
let arr=[1,2,3];
let[a,b,c]=arr;
console.log(a,b,c);
如果参数不全则从前往后赋值
申明一个对象:
let person={
name:"jack",
age:21,
language:['java','js','css']
}
常规获取值:
const name=person.name;
const age=person.age;
const language=person.language;
console.log(name,age,language);
更方便的取值:解构对象
const {name,age,language}=person
console.log(name,age,language);
当然,能用const的都能用let
let str="hello world";
console.log(str.startsWith("hello"));
console.log(str.endsWith("world"));
console.log(str.includes("he"));
startsWith 以字符串开始
endsWith 以字符串结束
includes 包含某字符串
多行字符串,使用反引号来申明:
let str=`hello
world
`;
console.log(str);
反引号还可以用于申明插值表达式
const name="lth";
const age=20;
const str=`I am ${name},I am ${age} years old`;
console.log(str);
使用 n a m e , 将 n a m e 变 量 插 入 到 指 定 位 置 , {name},将name变量插入到指定位置, name,将name变量插入到指定位置,{}里面可以是任意javascrpit表达式(变量,算数表达式,函数)
function func(){
return "这个一个函数";
}
const name="lth";
const age=20;
const str=`I am ${name},I am ${age} years old,${func()}`;
console.log(str);
如果是函数,会执行函数,并将函数的表达值返回(参与其他运算后,将值拼接到字符串中)
function func(a=3,b){
return `${a},这个一个函数`;
}
const name="lth";
const age=20;
const str=`I am ${name},I am ${age} years old,${func(2,3)}`;
console.log(str);
function用于定义函数,return用于定义返回值
参数列表中可以给参数默认值,这样在没有传这个参数的时候会使用默认值,参数列表赋值,无论有无默认值,都是从前往后依次赋值
不定参数
function func(a,...values){
console.log(values.length);
}
func(1,2);
func(2,3,4,5,6);
此时values其实就是一个数组,会将传入的值封装为一个数组
不定参数只能有一个并且必须放在最后
我们也可以给函数起一个别名,用等号进行赋值
var findPairs = function(nums, k) {
let f = function func(a,...values){
console.log(values.length);
}
f(1,2);
f(2,3,4,5,6);
};
有时候我们并不关心它的另一个名字(也避免占用命名空间)可以使用箭头函数来直接申明一个函数
var findPairs = function(nums, k) {
let f = function (a,...values){
console.log(values.length);
}
f(1,2);
f(2,3,4,5,6);
};
var findPairs = function(nums, k) {
let f =(a,...values)=>{
console.log(values.length);
}
f(1,2);
f(2,3,4,5,6);
};
其实这两种申明函数的方法效果都是一样的,都可以正常执行或者作为参数传入其他函数
var findPairs = function(nums, k) {
let f =(a,...values)=>{
console.log(values.length);
}
function hello(){
console.log("hello");
}
f(1,2);
f(2,3,4,5,6);
function fun(ff){
ff();
}
fun(hello)
};
如果箭头函数的方法体只有一条语句,大括号可以不写
推荐在方法体里面使用箭头函数,方法体外使用function的写法,这样可读性更好
解构表达式用于函数
我们传入对象的时候,传统方式直接将对象传递进去
let person={
name:"jack",
age:21,
language:['java','js','css']
};
let hello = (person) =>{
console.log(person.name);
};
hello(person);
我们也可以使用解构表达式来传递
let person={
name:"jack",
age:21,
language:['java','js','css']
};
let hello = ({name}) =>{
console.log(name);
};
hello(person);
可以直接输出name的值
相当于在调用函数,传递参数时,调用了{name}=person(其实所有语言的参数传递都可以理解为用=
,进行了一次赋值操作)
Object对象给我们提供了很多操作对象的方法
let person={
name:"jack",
age:21,
language:['java','js','css']
};
console.log(Object.keys(person));
console.log(Object.values(person));
console.log(Object.entries(person));
Object.keys(person) 获取对象的所有key,并封装成一个数组
Object.values(person) 获取对象的所有value,并封装成一个数组
Object.entries(person) 将对象封装成一个[key,value]的数组
对象合并
const target= {a:1};
const source1={b:2};
const source2={c:2};
Object.assign(target,source1,source2);
console.log(target);
第一个参数是目标对象,后面对象会依次合并到target里面,如果属性已经出现过,则会覆盖
申明对象:
如果对象中,变量名和值存放的变量名一直,我们可以使用下面这种方式简写
const name="zhangsan";
const age=20;
const person={name,age};
console.log(person);
等价于
const person={
name:name,
age:age
};
javascript平等地对待所有的变量,包含函数,对象,数组,数组,字符串,都视为一个变量
比如我们在对象里面定义一个函数
const name="zhangsan";
const age=20;
const person={
name:name,
age:age,
hello:"hello",
hello: function(){
console.log(this.name+":"+this.age);
},
hello2:()=>console.log(this.name+":"+this.age),
hello3(){
console.log(this.name+":"+this.age);
}
};
person.hello3();
这个函数和变量一样,都是这个对象的一个成员,成员之间用逗号分割,调用对象的成员的时候,如果不加括号表示是成员变量,加上括号表示调用的是成员方法,两种成员可以重名
当然也可以使用箭头函数来定义一个方法,但是箭头函数获取不到成员变量的值(所以一般不用这种写法)
hello2:()=>console.log(this.name+":"+this.age)
也可以使用和其他编程语言类似的一种写法
hello3(){
console.log(this.name+":"+this.age);
}
对象深拷贝
使用{…对象}来表示一个对象的深拷贝
let person={
name:"lth",
age:20,
pet:{
name:"josn"
}
};
let personbk={...person}
console.log(personbk)
对象属性合并:
obj1={name:"lth"}
obj2={name:"zech",age:20}
let p2={...obj1,...obj2}
console.log(p2)
将对象的属性拆出来进行合并,后面对象的属性会覆盖前面的对象
map:将每个元素处理后放入新数组返回
arr=[2,1,5,-5]
arr=arr.map(x=>x*2)
console.log(arr)
reduce:将数组的结果合并成一个数
arr=[2,1,5,-5]
arr=arr.map(x=>x*2)
let result=arr.reduce((pre,now)=>{
console.log(pre,now)
return pre+now;
},0)
console.log(result)
reduce第一个参数代表回调函数,含有两个参数:上一次运算的结果,当前处理的值
第二个参数是初始值
使用ajax时,会有成功和失败函数,我们可以使用promise对ajax的调用过程进行封装
使用格式:
let p=new Promise((resolve, reject) => {
resolve(data)
reject(err)
})
p.then((data)=>{
console.log(data);
}).catch(err=>console.log(err));
resolve的实现来自外面的then的参数
reject的实现来自于完美catch的实现
使用promise我们可以将ajax的成功和失败函数封装到resolve和reject中,并将函数的实现延迟到外面
我们可以使用promise将异步请求封装起来
使用ajax之前需要引入
function get(url, data,method) {
return new Promise((resolve, reject) => {
$.ajax({
url: url,
data: data,
method:method,
success(data) {
resolve(data)
},
error(err) {
reject(err)
}
})
})
}
然后我们就可以按照我们习惯的方式来发生异步请求:
get("./user.json","get").then((data) => {
console.log(data)
return get(`./user${data.userId}.json`,"get")
}).then((data) => {
console.log(data)
return get(`./course.json`,"get")
}).then((data) => {
console.log(data)
}).catch((err) => {
console.log(err)
})
每发送一个请求都return一个promise,这样调用结束后就可以继续.then进行下一步处理,javascript并不是一个严格的语言,函数参数不全并不会报错
在then里面传入请求成功后的方法,在catch里面传入请求失败的方法,参数都由get方法中ajax的回调的返回值来设置
export导出
export可以导出任何变量,方法,对象等
可以在申明变量的时候就直接export导出,就和public一样
导出后可以在其他js中导入
导出:
export const admin={
userName:"admin",
pwd:"123"
}
export default {
userName:"noOne",
pwd:"null"
}
如果其他文件导入时,没有找到指定的元素,则会默认导入default
导入使用:
import { admin } from "./user.js"
console.log(admin)
但是这个是ES6的语言,暂时无法测试
API文档:https://cn.vuejs.org/v2/api/?#v-for
MVVM思想:
M:model,包含数据和数据的一些操作
V:View,视图
VM:View-Model,视图和模型的双向操作,程序员无需关心中间的过程
Vue框架利用双向绑定,使我们不用过于关系DOM结构,只需要关心数据的操作,将视图元素的修改交给DOM中的监听器来完成
创建项目
npm init -y
安装vue依赖:
npm install vue@2
vue@2表示安装vue2,默认安装vue3,vue3没有vue.js
Document
{{name}},真帅
首先,我们要引入vue.js才能使用vue的功能
然后创建一个Vue的对象
el:代表element,里面使用选择器来表示vue管理的范围,其中#是id选择器,#hello表示管理id为hello的元素
data:代表模型中的数据,依然使用json的格式来设置里面的数据
然后在vue的管控范围内,我们就可以使用{{}}来从data中取值,放入到html元素中,此时,视图中的name和model中的name就形成了双向绑定的功能
{{name}},真帅
我们设置可以直接在控制台修改Vue对象的name属性,此时这个修改会直接反应到视图当中
上述例子体现了vue的申明式渲染功能,下面来介绍vue的双向绑定功能
{{name}},真帅,有{{num}}个人点赞
我们可以在input标签中使用v-model,表示输入的内容和num绑定
(注意,这个input的标签要在对应vue对象的管控范围内)
我们可以观察到vue的双向绑定功能:
一开始显示有2个人点赞,因为num在data中设置的初始值为2
当输入框的内容发送变化时,因为使用v-model绑定了num,所以vue对象中的num变量也会发生变化(控制台)(View改变Model)
vue对象中的num发生变化,下面显示的内容也随着发生变化(Model改变View)
{{name}},真帅,有{{num}}个人点赞
比上一小节的多了一个元素button
使用v-on表示绑定一个事件,click表示是点击事件,双引号""中编写javascript语句,可以使用data中的变量,这里使用num++多个语句直接可以使用分号隔开
{{name}},真帅,有{{num}}个人点赞
v-on中可以使用任何javascript语句直接对data中的元素进行操作,这些语句也包括我们在mothods中申明的方法,上面我们在methods中申明了一个方法cancle,然后在中进行调用
小结:
v-xx:代表一条指令
Vue使用步骤:
el:绑定元素
data:绑定数据
methods:绑定方法
下载vue2 Snippets,vue Snippets
下载浏览器插件的vue插件:
https://devtools.vuejs.org/guide/installation.html#chrome
例如:
Edge:Vue.js devtools - Microsoft Edge Addons
Document
我们可以在data中申明一个类似html标签的字符串,如果直接用{{}}则msg的内容会被转译,不会以html元素的方式呈现出来,我们想要它以html元素的方式呈现出来,可以使用v-html标签,这样浏览器就会以html元素的方式来看待这个字符串
相反,我们可以使用v-text来让浏览器按照它原来的方式显示字符串,效果和{{}}插值表达式相同
插值表达式和v-text有一个小小的不同是,如果网速很慢,插值表达式会先显示为{{name}},再出现实际的值,也就是插值闪烁的现象。而使用v-text则没有这个问题
插值表达式可以调用js语法,但是必须有返回值,插值表达式可以使用data中的任何数据和methods中的任何方法
Document
{{hello(name)}}
{{1+1}}
{{'1'+1}}
{{5*5}}
插值表达式里面只能是一条有返回值的语句
我们可以使用v-bind绑定标签内部的所有属性,比如href,class,style等
下面是一个绑定href的例子
使用v-bind:href来和data中的link来绑定
v-bind:class
可以再大括号中使用{class名:true/false,class名:true/false}来决定这个属性是否会出现再对应绑定的属性里面
同时我们可以使用data里面的值来设置true或false
gogogo
active
上述b标签,最后呈现出来的结果就是下面这样:
active
一个标签可以有多个class,每个class用逗号分隔
短横线-
在这个里面是非法的,我们可以使用单引号将属性值引起来,或者也可以转换为驼峰命名法
我们在v-bind里面设置的多个属性都用{}括起来,然后使用逗号分割,vue在识别的时候会自动转换为html可以识别的分隔符
例如class使用空格分割,style使用分号分割
gogogo
active
你好
使用v-bind:style即可动态设置风格:
你好
其中,v-bind其实可以省略,简写成:style,:class,效果是一样的,开发时推荐使用这种方式
gogogo
active
你好
v-bind类型的组件都是单向绑定,即model可以影响页面元素,但是页面元素修改后不会影响model
只有输入框和表单项是双向绑定的,使用v-model来实现双向绑定
Java
PHP
C++
{{language.join(",")}}
v-model表示和data种的一个数组类型绑定,checkbox表示一个单选框
Java
value字段设置这个输入的值,也就是放入language数组中的值
上述代码即可实现,选择一个复选框,放入一个元素的效果,数组的顺序就是选择的顺序
{{language.join(",")}}
数组得到join方法可以让我们使用我们指定的参数对数组元素进行拼接
踩
有{{num}}个人点赞
前面我们使用了v-on来进行了基础的功能测试,v-on用于绑定事件,冒号后面用于指定事件的名称,click即为点击事件,双引号里面是要执行的语句和要执行的函数,函数可以加括号也可以不加
v-on可以用@代替
嵌套的div:
踩
有{{num}}个人点赞
大div
小div
style="border: 50px solid red;"
表示边框大小为50,添加边框,颜色是red
我们在data里面注入console对象,这样就可以直接在@click中使用
我们可以看到点击大div时,会出现大div被点击,小div被点击时,同时输出大div被点击和小div被点击
因为小div也是大div的一部分,这种现象叫做事件冒泡
可以通过添加后缀来对事件进行设置
将@click改成@click.stop即可方式事件冒泡到父类div
大div
小div
并且这个修饰符可以叠加
大div
小div
gogogo
点击a标签后,会先弹出被点击,然后弹出大div被点击,最后跳转到baidu页面
如果我们不想让他执行父类的同类型事件,可以加上stop
如果想阻止标签的默认行为,可以加上prevent
后面我们指定的事件是一定会发生的事件
将前面的a标签改成:
gogogo
即可出现点击后只弹出"被点击"的效果
比如后面加上.once后,只有第一次点击有效果,后面点击就会失效
键盘的每一个案件都有自己的编码,我们叫他keycode:键盘KeyCode对照表 - 走看看 (zoukankan.com)
数字和字母的keyCode就是其对应的ASCII码
vue也记录了一些常见的keyCode
这样我们将光放到输入框后,按一下上即可+2,按一下下即可-2
{{num}}
除了单个键点击以外,我们还可使用组合键
{{num}}
组合键就是在后面按照键的顺序依次添加修饰符,@后面的修饰符则是最后需要执行的动作,例如click表示点击,@keyup表示抬起按键
可以使用@ctrl,@alt,@shift来自定义我们的组合键
我们在vue对象中拿到了一些数据:users
let vm = new Vue({
el: "#app",
data: {
users: [
{ name: "lth", age: 20 },
{ name: "zech", age: 2 },
{ name: "netty", age: 3 },
{ name: "java", age: 4 }
]
}
})
我们需要使用列表将数据展示出来,这时候就可以使用v-for标签、
-
{{user.name}} {{user.age}}
v-for=“user in users”,表示遍历users中的每个对象,用user来表示,在标签内部使用插值表达式来获取对象的每个属性的值
可以加上第二个元素表示下标,可以任意取名,这里叫做index
-
{{index}} {{user.name}} {{user.age}}
不仅是列表标签,任何标签都可以动态生成:
{{index}} {{user.name}} {{user.age}}
除了遍历数组,我们还可以遍历对象:
{{index}}
{{k}} {{v}}
遍历对象时使用(v,k,i)分别获取值,键,索引(这里值在前)
我们在使用v-for时,尽量设置:key来提高渲染效率,:key是一个可以唯一区分的键,一般可以设置为主键,如果没有唯一可以区分的键,可以使用索引,一般使用索引即可
{{index}}
{{k}} {{v}}
使用v-if
使用v-show
v-if和v-show为true的时候都表示展示元素,表示false时,都表示隐藏元素
v-if为false时,会删除DOM元素
v-show为false时,会修改style
v-show效率更高
v-if功能更强,安全性更好
同时v-if还可以配合v-else和v-else-if来完成更复杂的操作
v-if ,v-else-if,v-else可以配合起来,在DOM层完成分支操作,即在后面写上逻辑表达式
带有这三个标签的元素需要放在一起,后面的else和else-if才能发挥作用
使用v-if
使用v-show
当前的num的值为{{randomNum}}
randomNum>=0.7
randomNum>=0.5
randomNum<0.5
v-if和v-for可以配合起来使用,对列表中遍历的元素进行筛选
{{index}}
{{k}} {{v}}
v-if的优先级小于v-for,所以会先执行v-for,得到用于遍历的对象user,index等,然后可以使用这些变量进行条件判断
可以用于输入数字型的参数
西游记的价格是{{xyjPrice}},数量是
红楼梦的价格是{{shzPrice}},数量是
总价格为{{sumPrice}}
可以在computed里面设置方法,用于计算data中的数据,如果使用到的任意一个数据发生变化,都会调用这个方法重新计算,注意,这里虽然是一个方法,但是使用的时候不能加上括号,要把它当成一个变量来使用
效果:
西游记的价格是{{xyjPrice}},数量是
红楼梦的价格是{{shzPrice}},数量是
总价格为{{sumPrice}}
在vue对象中添加一个属性watch,用于监听data和computed中的变量,只要data或者computed中的值发生了变化,就会触发watch中的回调函数,回调函数中一共有两个参数(newVal,oldVal)分别代表新值和旧值
例如上述案例中,当新的值大于3时,就会显示消息,并维持数量等于3,这样用户就不能让xyjNum大于3
西游记的价格是{{xyjPrice}},数量是
红楼梦的价格是{{shzPrice}},数量是
总价格为{{sumPrice}}
数量{{xyjNum | xyjFilter}}
我们可以申明一个过滤器对值进行一些处理后返回对应的结果:
filters:{
xyjFilter(num){
if(num>5) return "好多";
else return "好少";
}
}
使用在插值表达式中使用管道符号,将参数传递给过滤器,每当参数发生变化时,都会重新运行过滤器函数,并得到插值表达式的值
其实使用方法也能得到相同的结果:
methods:{
xyjFilter(num){
if(num>5) return "好多";
else return "好少";
}
}
西游记的价格是{{xyjPrice}},数量是
红楼梦的价格是{{shzPrice}},数量是
总价格为{{sumPrice}}
数量{{xyjFilter(xyjNum)}}
申明全局的过滤器:
在filters中申明的过滤器只有在当前这个vue对象的管控范围内才能使用,如果我们想申明一个所有vue对象都能使用的过滤器,可以使用下面这个方法,申明一个全局的filter:
Vue.filter("numFilter",function(num){
if(num<2){
alert("太少啦,多买点")
return "太少啦"
}
return "还不错"
})
然后可以和普通的filter一样使用:
西游记的价格是{{xyjPrice}},数量是
红楼梦的价格是{{shzPrice}},数量是
总价格为{{sumPrice}}
数量{{ xyjNum | numFilter}}
小细节:
{{num}}:得到的结果是2
如果两个vue的作用域重叠了,相同的数据会以外面为准,即内层的数据会被外层的数据覆盖
我们可以将页面拆分成若干独立的组件,整个页面由这些组件构成,我们可以把一些公共的组件提取出来,供所有的页面使用,一方面减少了代码量,并且也方便我们对组件进行统一的修改
申明一个全局的组件:
Vue.component("mybutton",{
template: '',
data() {
return {
count: 1
};
}
})
第一个参数表示组件名,申明后可以使用在任何vue实例的管理范围内(因为这是全局申明的组件)使用
来使用组件
第二个参数表示具体的组件信息,其实本质上就是一个vue实例,每个组件都是一个vue实例,这里的data必须使用函数的形式,这样每个组件都有自己独有的一份数据,如果使用data:{}的形式,则所有组件会共享一份数据,这样就达不到组件的效果
我们之前申明的vue对象,也可以采用函数的写法,不过这里就算采用的函数的写法,在它的管理范围内,数据是唯一的
全局申明的组件,所有vue实例的管理范围内都能使用
申明一个组件对象:
这个对象需要采用vue对象的格式来编写
const buttonCounter={
template: '',
data() {
return {
count: 1
};
}
}
这样只是申明了一个普通的对象,我们在vue对象所在的范围内引入才可以使用
let vm=new Vue({
el:"#app",
data:{
count:1
},
components:{
'button-counter':buttonCounter
}
})
在vue对象中,在components里面以kv的形式声明组件(组件名:组件),然后即可在这个vue对象的挂载范围内使用
注意,html标签是不区分大小写的,所有的html标签在识别时都会转换为小写,如果申明的时候用的是大写,使用的时候也用的是大写,两者虽然相同,但是会找不到,所以为了避免误会,组件名称必须使用小写(当然,使用的时候,如果使用了大写也能识别)
一个Vue实例的生命周期需要经过以上步骤:
案例:
Document
{{num}}
{{name}},有{{num}}个人点赞
使用生命周期的方法为在和methods同级属性中申明对应名称的方法,vue实例就会自动触发这些方法
beforeCreate:此时数据和方法都没有加载进vue实例,无法使用
created:此时方法和实例都加载进了vue实例,可以调用,但此时还没有将{{num}}渲染进去
beforeMount:此时同样还没有渲染,但是准备开始渲染了
mounted:此时页面已经渲染完成,开始进入监听状态
beforeUpdate:我们点击按钮后,会先触发这个事件,此时模型数据已经发送看变化,而页面元素还没有变
updated:此时页面元素也已经发送了变化
我们平时开发的时候一般使用模块化环境进行开发,这样开发起来效率更高
全局安装webpack
npm install webpack -g
全局安装vue的命令行
npm install -g @vue/cli-init
然后我们就可以使用
vue init webpack
来快速搭建一个脚手架工程
这里我们要找到npm的全局安装目录,然后将vue.cmd所在的文件夹添加进环境变量中,这样才能使用vue命令行
然后我们下面会遇到一些选项:
名字是否正确?修改名称话直接在后面添加,否则直接回车
? Project name (vue-demo)
添加项目表述,否则使用默认值
Project description (A Vue.js project)
确认作者信息
? Author (释然 <[email protected]>)
确认编译环境,这里选择上面那个(上下移动)
后面总之一路Y就好,把该装的依赖都装上,最后选择npm作为我们的包管理工具,这里就关闭语法检查和单元测试(因为对格式要求太严格)
然后再当前目录下就会生成一个vue-demo
大致分为一下部分:
build:项目的打包依赖,比如webpack
node_moudels:这个很常见了,npm下载的依赖
config:相关的配置文件,比如运行的端口等
src:代码编写
static:图片等静态文件
index.html:首页
.babelrc:babel的配置信息
package.json:依赖以及版本
package-lock.json
关键文件
router:路由
index.html只有一条语句:
<div id="app">div>
显然这是由js进行支撑,这个对应于一个main.js,内容如下:
import Vue from 'vue'
import App from './App'
import router from './router'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: ' '
})
申明了一个app组件,模板使用了app组件,组件长什么样,模板就是什么样,组件定义在对应的vue文件中,例如下面的app.vue
由三个部分组成:
template:页面要显示成什么样子,里面就按照html的语法格式来写
script:javascript代码
style:样式
:路由视图
表示下面显示什么要根据我们的url动态决定,因而我们需要一个路由规则来设置哪个路由显示哪个组件
而路由规则在main.js中导入:
import router from './router'
并在创建vue的时候使用了这个路由规则
而这个路由规则定义在router目录下的任意一个js里面
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
}
]
})
上面规定了,当我们访问/时,显示HelloWorld组件,于是就有了我们的访问规则
@就代表我们的src目录,组件就定义在components目录下,一个vue文件就是一个组件
编写一个vue文件来编写我们自己的组件
Hello,{{ name }}
添加路由,需要使用import导入组件,@表示src目录
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Hello from '@/components/Hello'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
},
{
path: '/hello',
name: 'Hello',
component: Hello
}
]
})
这样在App.vue中,就可以根据url显示组件:
注意,路由前面一定会有一个#
我们还可以通过
来动态调整当前的路由,从而动态调整下面
根据路由动态调整的组件
去Hello
去首页
https://element.eleme.cn/#/zh-CN/component/installation
安装element-UI
npm i element-ui -S
i是install的简写
也可以使用来导入,但我们是模块化开发,不使用这个
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<script src="https://unpkg.com/element-ui/lib/index.js">script>
开发前要导入组件和依赖
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use用于安装插件,需要在new Vue之前使用
我们可以参照官方文档来编写代码组件,比如我们要使用单选框
我们在上面的template中我们想要的位置导入上面的组件,下面需要的位置加上属性就可以使用
go
java
我喜欢{{radio==2?"java":"go"}}
单选框的v-model绑定的元素表示,如果哪个被选中了,绑定的值就变成label
我们使用element-ui来快速开发一个后台管理系统
Vue对象中template的作用:
这么写表示让id为app的div显示template中的内容
在这里挑选一个布局作为我们开发的大背景:https://element.eleme.cn/#/zh-CN/component/container
这里选择一个常见的布局结构
导航一
分组一
选项1
选项2
选项3
选项4
选项4-1
导航二
分组一
选项1
选项2
选项3
选项4
选项4-1
导航三
分组一
选项1
选项2
选项3
选项4
选项4-1
查看
新增
删除
王小虎
其实就是将官网的代码复制过来,注意template标签不要丢
使用这个组件的代码是在main.js,所以需要在main的js里:
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI)
我们来分析一下它的代码:
最外层是一个大的布局容器:
然后又分为一个侧边栏和另一个容器
另一个容器包含两部分:导航栏和主页面
导航栏:
查看
新增
删除
王小虎
里面有一个下拉框,里面有一些对应的属性
主界面是一个表格:
el-table申明一个表格
:data=“tableData” 表示这里面的数据来自tableData变量
标签内部申明了要展示哪些列
prop表示这一列展示对象中的哪个属性
label表示这一列的标题
width表示这一列的宽度
表格的更多样式和功能可以参考官方文档:https://element.eleme.cn/#/zh-CN/component/table#table-column-attributes
目前主页面是一个表格,但是显然,我们想要主界面的内容随着路由的更改而发生变化,因而我们需要将中间的部分提取成组件,然后建立路由映射来更改中间的内容,这个方式我们前面也讲过使用
即可
然后将表格内容抽取成组件:
抽取组件后,我们需要设置路由来显示url和表格的映射关系
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import Hello from '@/components/Hello'
import ElementUI from 'element-ui'
import UserTable from '@/components/UserTable'
Vue.use(Router)
Vue.use(ElementUI)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
},
{
path:'/usertable',
name:'UserTable',
component: UserTable
},
{
path:'/hello',
name:'Hello',
component: Hello
}
]
})
在导航菜单处,设置调整的路由
这里需要参考官方文档
官方文档:https://element.eleme.cn/#/zh-CN/component/menu#menu-group-attribute
我们需要在el-menu标签处,添加属性router=true,这样下面的导航栏就可以通过设置index来设置跳转的路由
用户列表
hello
点击后即可显示不同的界面
主界面组件的完整代码:
导航一
用户操作
用户列表
hello
选项3
选项4
选项4-1
导航二
分组一
选项1
选项2
选项3
选项4
选项4-1
导航三
分组一
选项1
选项2
选项3
选项4
选项4-1
查看
新增
删除
王小虎
在main.js里面申明了App组件,并用template表示带有id="app"的标签使用这个组件
Vue.use(ElementUI)
new Vue({
el: '#app',
router,
components: { App },
template: ' '
})
因而index.html只需要一行代码即可展示界面