谷粒学苑一
# 谷粒学苑一
一、第一天
1、项目商业模式
2、项目功能模块
3、项目技术要点
项目采用前后端分离开发
后端技术:
- SpringBoot
- springcloud
- MyBatisplus
- spring security
- redis
- maven
- easyExecl
- jwt
- OAuth2
前端技术:
- vue
- elementui
- axios
- nodejs
- ……
其他技术:
- 阿里云oss
- 阿里云视频点播服务
- 阿里云短信服务
- 微信支付和登录
- docker
- git
- jenkins
4、mybatis-plus(入门复习)
1、创建数据库
mybatis_plus
2、创建user表
sql文件:
/* Navicat Premium Data Transfer Source Server : localhost_3306 Source Server Type : MySQL Source Server Version : 50729 Source Host : localhost:3306 Source Schema : mybatis_plus Target Server Type : MySQL Target Server Version : 50729 File Encoding : 65001 Date: 06/05/2023 13:06:14 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for t_user -- ---------------------------- DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ( `uid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `user_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名', `age` int(11) NULL DEFAULT NULL COMMENT '年龄', `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱', `sex` int(11) NULL DEFAULT NULL, `is_deleted` int(11) NULL DEFAULT 0, PRIMARY KEY (`uid`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of t_user -- ---------------------------- INSERT INTO `t_user` VALUES (1, 'lxg1a', 19, 'lxg1@qq.com', NULL, 1); INSERT INTO `t_user` VALUES (2, 'lxg2', 20, 'lxg2@qq.com', NULL, 1); INSERT INTO `t_user` VALUES (3, 'lxg3a', 21, 'lxg3@qq.com', NULL, 1); INSERT INTO `t_user` VALUES (4, 'lxg4', 22, 'lxg4@qq.com', NULL, 0); INSERT INTO `t_user` VALUES (5, '小明', 23, 'user@example.com', NULL, 0); INSERT INTO `t_user` VALUES (6, 'lxg6', 24, 'lxg6@qq.com', NULL, 0); INSERT INTO `t_user` VALUES (7, 'lxg7', 25, 'lxg7@qq.com', NULL, 0); INSERT INTO `t_user` VALUES (8, 'lxg8', 26, 'lxg8@qq.com', NULL, 0); INSERT INTO `t_user` VALUES (9, 'lxg9', 27, 'lxg9@qq.com', NULL, 0); INSERT INTO `t_user` VALUES (10, 'lxg', 21, 'xiaolin@qq.com', NULL, 0); INSERT INTO `t_user` VALUES (11, '小明', 22, 'user@example.com', NULL, 0); INSERT INTO `t_user` VALUES (12, 'xiaolin', NULL, NULL, NULL, 1); INSERT INTO `t_user` VALUES (13, 'admin', 21, NULL, 1, 0); INSERT INTO `t_user` VALUES (14, 'lxg', 21, 'xiaolin@qq.com', NULL, 0); SET FOREIGN_KEY_CHECKS = 1;
3、创建项目
创建SpringBoot项目——MyBatisPlusDemo(2.7.11)
4、引入依赖
SpringBoot和MP的依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- mybatis-plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency> <!-- mysql --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies>
5、记得去下载lombok插件
6、配置文件application.properties
# mysql spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=123456
7、编写代码
User
package com.lxg.mybatisplusdemo.entity; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @auther xiaolin * @creatr 2023/5/6 13:25 */ @Data @NoArgsConstructor @AllArgsConstructor @TableName(value ="t_user") public class User { private Long id; private String userName; private Integer age; private String email; private Integer sex; private Integer isDeleted; }
UserMapper
package com.lxg.mybatisplusdemo.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.lxg.mybatisplusdemo.entity.User; import org.apache.ibatis.annotations.Mapper; /** * @auther xiaolin * @creatr 2023/5/6 13:28 */ @Mapper public interface UserMapper extends BaseMapper<User> { }
主启动类
package com.lxg.mybatisplusdemo; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("com.lxg.mybatisplusdemo.mapper") public class MyBatisPlusDemoApplication { public static void main(String[] args) { SpringApplication.run(MyBatisPlusDemoApplication.class, args); } }
测试类
package com.lxg.mybatisplusdemo; import com.lxg.mybatisplusdemo.entity.User; import com.lxg.mybatisplusdemo.mapper.UserMapper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest class MyBatisPlusDemoApplicationTests { @Autowired private UserMapper userMapper; //查询user表中的所有数据 @Test public void findAll() { List<User> users = userMapper.selectList(null); System.out.println(users); } }
8、添加mybatisplus日志配置
#mybatis-plus日志 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
9、实现添加操作
//添加操作 @Test public void addUser() { User user = new User(); user.setUserName("小明"); user.setAge(18); user.setEmail("test@qq.com"); user.setSex(1); int insert = userMapper.insert(user); if (insert > 0) { System.out.println("添加成功 "+insert); } }
10、主键策略
1、自动增长
如果想要让其自增+1,按如下配置:
数据库配置自动增长
优点:
1)简单,代码方便,性能可以接受。
2)数字ID天然排序,对分页或者需要排序的结果很有帮助。
缺点:
1)不同数据库语法和实现不同,数据库迁移的时候或多数据库版本支持的时候需要处理。
2)在单个数据库或读写分离或一主多从的情况下,只有一个主库可以生成。有单点故障的风险。
3)在性能达不到要求的情况下,比较难于扩展。(不适用于海量高并发)
4)如果遇见多个系统需要合并或者涉及到数据迁移会相当痛苦。
5)分表分库的时候会有麻烦。
6)并非一定连续,类似MySQL,当生成新ID的事务回滚,那么后续的事务也不会再用这个ID了。这个在性能和连续性的折中。如果为了保证连续,必须要在事务结束后才能生成ID,那性能就会出现问题。
7)在分布式数据库中,如果采用了自增主键的话,有可能会带来尾部热点。分布式数据库常常使用range的分区方式,在大量新增记录的时候,IO会集中在一个分区上,造成热点数据。
优化方案:
1)针对主库单点,如果有多个Master库,则每个Master库设置的起始数字不一样,步长一样,可以是Master的个数。比如:Master1 生成的是 1,4,7,10,Master2生成的是2,5,8,11 Master3生成的是 3,6,9,12。这样就可以有效生成集群中的唯一ID,也可以大大降低ID生成数据库操作的负载。
2、UUID
UUID(Universally Unique Identifier)是一种用于标识信息的标准化方法,也称为GUID(Globally Unique Identifier)。它是一个128位的数字,通常表示为32个十六进制数字,用连字符分隔成5个部分,例如:123e4567-e89b-12d3-a456-426655440000。
UUID可以用于生成唯一的标识符,例如在数据库中用作主键,或在分布式系统中用于标识不同的节点。由于UUID的唯一性和无序性,它在许多领域都得到广泛应用,例如软件开发、网络通信和数据存储等。
每次生成随机唯一的值。
优点:
1)简单,代码方便。
2)生成ID性能非常好,基本不会有性能问题。
3)全球唯一,在遇见数据迁移,系统数据合并,或者数据库变更等情况下,可以从容应对。
缺点:
1)没有排序,无法保证趋势递增。
2)UUID往往是使用字符串存储,查询的效率比较低。
3)存储空间比较大,如果是海量数据库,就需要考虑存储量的问题。
4)传输数据量大
5)不可读
3、redis实现
当使用数据库来生成ID性能不够要求的时候,我们可以尝试使用Redis来生成ID。这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作 INCR和INCRBY来实现。
可以使用Redis集群来获取更高的吞吐量。假如一个集群中有5台Redis。可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5。各个Redis生成的ID为:
A:1,6,11,16,21
B:2,7,12,17,22
C:3,8,13,18,23
D:4,9,14,19,24
E:5,10,15,20,25
这个,随便负载到哪个机确定好,未来很难做修改。但是3-5台服务器基本能够满足器上,都可以获得不同的ID。但是步长和初始值一定需要事先需要了。使用Redis集群也可以方式单点故障的问题。
另外,比较适合使用Redis来生成每天从0开始的流水号。比如订单号=日期+当日自增长号。可以每天在Redis中生成一个Key,使用INCR进行累加。
优点:
1)不依赖于数据库,灵活方便,且性能优于数据库。
2)数字ID天然排序,对分页或者需要排序的结果很有帮助。
缺点:
1)如果系统中没有Redis,还需要引入新的组件,增加系统复杂度。
2)需要编码和配置的工作量比较大。
4、mp默认策略——雪花算法
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake。雪花算法支持的TPS可以达到419万左右(2^22*1000)。
mp自动生成了id,默认使用雪花算法生成19位
优点:
1)不依赖于数据库,灵活方便,且性能优于数据库。
2)ID按照时间在单机上是递增的。
缺点:
1)在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,在算法上要解决时间回拨的问题。
参考资料:分布式系统唯一ID生成方案汇总:https://www.cnblogs.com/haoxinyue/p/5208136.html
- AUTO:自动增长
- ASSIGN_ID:雪花算法
- ASSIGN_UUID:UUID
- INPUT:自行设置id值
- NONE:没有策略,自行设置
11、实现修改操作
//修改操作 @Test public void updateUser() { User user = new User(); user.setId(1L); user.setUserName("小红"); user.setAge(20); int row = userMapper.updateById(user); if (row > 0) { System.out.println("修改成功 "+row); } }
12、自动填充
项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。
我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作:
1、数据库表中添加自动填充字段
在User表中添加datetime类型的新的字段create_time、update_time
2、实体类添加注解
//驼峰命名法则,数据库字段为create_time,实体类为createTime private Date createTime; private Date updateTime;
3、填充方式
-
原始方式需要手动设置set
-
使用数据库参数
create_time将类型设置为timestamp,默认值设置为CURRENT_TIMESTAMP
update_time将类型设置为timestamp,默认值设置为CURRENT_TIMESTAMP,另外设置根据当前时间戳进行更新
-
使用注解方式
第一步:
//驼峰命名法则,数据库字段为create_time,实体类为createTime @TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; 第二步:
创建类,实现接口MetaObjectHandler,重写接口的方法
package com.lxg.mybatisplusdemo.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.util.Date; /** * @auther xiaolin * @creatr 2023/5/6 14:52 */ @Component public class MyMetaObjectHandler implements MetaObjectHandler{ //mp执行添加操作,这个方法会执行 @Override public void insertFill(MetaObject metaObject) { this.setFieldValByName("createTime",new Date(),metaObject); this.setFieldValByName("updateTime",new java.util.Date(),metaObject); } //mp执行修改操作,这个方法会执行 @Override public void updateFill(MetaObject metaObject) { this.setFieldValByName("updateTime",new Date(),metaObject); } }
13、乐观锁
主要解决:丢失更新
丢失更新:两个线程同时对一个数据进行更新,先提交事务的线程所做的更新操作会被后提交事务的所覆盖
乐观锁和悲观锁都是并发控制的方法。
乐观锁是指在并发操作中,假设数据不会发生冲突,直接进行操作,而在提交时检查数据是否被其他进程修改过,如果被修改则回滚操作。乐观锁通常采用版本号或时间戳等方式实现。
悲观锁是指在并发操作中,假设数据会发生冲突,因此在操作前先获取锁,保证其他进程不能同时修改数据,操作完成后再释放锁。悲观锁通常采用数据库中的行级锁或表级锁实现。
乐观锁适用于读多写少的场景,因为冲突的概率较小,可以减少锁的竞争,提高并发性能。而悲观锁适用于写多读少的场景,因为冲突的概率较大,需要通过锁来保证数据的正确性。
常见的乐观锁和悲观锁有以下几种:
乐观锁:
-
版本号机制:在数据表中增加一个版本号字段,每次更新数据时,将版本号加1,当提交时检查版本号是否一致,如果不一致则说明数据已被其他进程修改过,需要回滚操作。
-
时间戳机制:在数据表中增加一个时间戳字段,每次更新数据时,将时间戳更新为当前时间,当提交时检查时间戳是否一致,如果不一致则说明数据已被其他进程修改过,需要回滚操作。
悲观锁:
- 行级锁:在数据库中使用行级锁,当一条数据被锁定时,其他进程不能修改该数据,只有锁被释放后才能进行修改。
- 表级锁:在数据库中使用表级锁,当一张数据表被锁定时,整张表中的数据都不能被修改,只有锁被释放后才能进行修改。
- 互斥锁:在程序中使用互斥锁,当一个线程获取到锁时,其他线程不能访问共享资源,只有锁被释放后才能进行访问。
具体实现
1、数据库表添加乐观锁版本号字段version int 11
2、对应实体类添加属性version
private Integer version;//版本号
3、在实体类版本号属性上添加注解
@Version private Integer version;//版本号
4、配置乐观锁插件
package com.lxg.mybatisplusdemo.config; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @auther xiaolin * @creatr 2023/5/6 17:47 */ @Configuration @MapperScan("com.lxg.mybatisplusdemo.mapper") public class MpConfig { //乐观锁插件 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }
14、测试乐观锁
//测试乐观锁 @Test public void testOptimisticLocker() { //根据id查询数据 User user = userMapper.selectById(1654735201991467017L); //进行修改 user.setUserName("testOptimisticLocker"); userMapper.updateById(user); }
必须先查后改
15、mp简单查询
1、根据id查询
//根据id查询数据 // User user = userMapper.selectById(1654735201991467017L);
2、通过多个id批量查询
//多个id批量查询 @Test public void testSelectDemo1() { List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L)); System.out.println(users); }
3、通过map封装查询条件
//简单的条件查询map @Test public void testSelectByMap() { HashMap<String, Object> map = new HashMap<>(); map.put("user_name", "zhangsan"); map.put("age", 18); List<User> users = userMapper.selectByMap(map); System.out.println(users); }
4、分页查询
①配置分页插件
//分页插件 @Bean public MybatisPlusInterceptor mpInterceptor() { //1.定义Mp拦截器 MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor(); //2.添加具体的拦截器 mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return mpInterceptor; }
②编写分页代码
直接new page对象,传入两个参数:
当前页和每页显示记录数
然后调用mp方法实现分页查询
//测试分页查询 @Test public void testPage(){ //1.创建page对象 //传入两个参数:当前页 和 每页显示记录数 Page<User> page = new Page<>(1,3); //调用mp分页查询的方法 //调用mp分页查询过程中,底层封装 //把分页所有数据封装到page对象里面 userMapper.selectPage(page,null); //通过page对象获取分页数据 System.out.println(page.getCurrent());//当前页 System.out.println(page.getRecords());//每页数据list集合 System.out.println(page.getSize());//每页显示记录数 System.out.println(page.getTotal());//总记录数 System.out.println(page.getPages());//总页数 System.out.println(page.hasNext());//是否有下一页 System.out.println(page.hasPrevious());//是否有上一页 }
16、删除
1、物理删除
①单个删除
@Test public void testDeleteById(){ int result = userMapper.deleteById(1654735201991467017L); System.out.println(result); }
②批量删除
//批量删除 @Test public void testDeleteBatchIds(){ int result = userMapper.deleteBatchIds(Arrays.asList(1L, 2L)); System.out.println(result); }
2、逻辑删除
①数据库新增isdeleted字段,默认值0
②配置文件增加配置
yml格式
mybatis-plus: global-config: db-config: logic-delete-field: isDeleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2) logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
properties格式
#配置这条就不需要在每个实体类上加@TableLogic注解 mybatis-plus-global-config.db-config.logic-delete-field-name=isDeleted mapbatis-plus-global-config.db-config.logic-delete-value=1 mapbatis-plus-global-config.db-config.logic-not-delete-value=0
③添加注解
//逻辑删除 @TableLogic private Integer isDeleted;
如果是旧版还需要添加配置
//逻辑删除插件 /** * 旧版 */ /*@Bean public ISqlInjector sqlInjector() { return new LogicSqlInjector(); }*/
17、性能分析
旧版本
配置插件
/** * SQL执行效率插件 */ @Bean @Profile({"dev","test"})// 设置 dev test 环境开启,保证效率 public PerformanceInterceptor performanceInterceptor() { PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); performanceInterceptor.setMaxTime(100); // ms 设置 SQL 执行的最大时间,如果超过了就不执行 performanceInterceptor.setFormat(true); // 开启格式化支持 return performanceInterceptor; }
配置文件
#环境配置:dev、test、prod spring.profiles.active=dev
新版本
新版已没有以上配置,只有另外一个sql执行分析
①添加maven依赖
<dependency> <groupId>p6spy</groupId> <artifactId>p6spy</artifactId> <version>3.9.0</version> </dependency>
②application.properties修改配置
spring.datasource.driver-class-name= com.p6spy.engine.spy.P6SpyDriver spring.datasource.url=jdbc:p6spy:mysql://localhost:3306/mybatis_plus?useSSL=false&serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=123456
③新增spy.properties配置文件
#3.2.1以上使用 modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory #3.2.1以下使用或者不配置 #modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory # 自定义日志打印 logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger #日志输出到控制台 appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger # 使用日志系统记录 sql #appender=com.p6spy.engine.spy.appender.Slf4JLogger # 设置 p6spy driver 代理 deregisterdrivers=true # 取消JDBC URL前缀 useprefix=true # 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset. excludecategories=info,debug,result,commit,resultset # 日期格式 dateformat=yyyy-MM-dd HH:mm:ss # 实际驱动可多个 #driverlist=org.h2.Driver # 是否开启慢SQL记录 outagedetection=true # 慢SQL记录标准 2 秒 outagedetectioninterval=2
以上配置完,执行sql时便会打印sql执行情况耗时等
18、mp实现复杂条件查询
1、Wrapper介绍
-
Wrapper
: 条件构造抽象类,最顶端父类-
AbstractWrapper
: 用于查询条件封装,生成 sql 的 where 条件-
QueryWrapper
: 查询条件封装 -
UpdateWrapper
: Update 条件封装 -
AbstractLambdaWrapper
: 使用Lambda 语法
-
-
-
LambdaQueryWrapper
:用于Lambda语法使用的查询Wrapper -
LambdaUpdateWrapper
: Lambda 更新封装Wrapper
具体看mybatis-plus笔记:
https://www.yuque.com/u32200234/vbsrh6/koohmxsuqw7r8fh2?singleDoc#
或官网:
条件构造器 | MyBatis-Plus (baomidou.com)
2、使用QueryWrapper
创建QueryWrapper对象
调用方法实现具体操作
//mp实现复杂查询操作 @Test public void testSelectQuery(){ //创建QueryWrapper对象 QueryWrapper<User> wrapper = new QueryWrapper<>(); //通过QueryWrapper设置条件 //ge、gt、le、lt(大于等于、大于、小于等于、小于) //查询age>=30记录 //第一个参数字段名称,第二个参数设置值 //wrapper.ge("age",30); //eq、ne(等于、不等于) //wrapper.eq("name","zhangsan"); //between(范围查询) //查询年龄 20-30 //wrapper.between("age",20,30); //like(模糊查询)likeLeft likeRight(左模糊、右模糊) //wrapper.like("name","张"); //orderByDesc(降序) orderByAsc(升序) orderBy(根据某个条件排序) // wrapper.orderByDesc("id"); //last(拼接sql语句) //wrapper.last("limit 1"); //指定要查询的列 wrapper.select("id","name"); List<User> users = userMapper.selectList(wrapper); System.out.println(users); }
二、第二天
1、前后端分离开发
2、讲师管理模块(后端)
2.1、准备工作
1、创建数据库,创建讲师数据库表
创建数据库guli
创建讲师表:
# # Structure for table "edu_teacher" # CREATE TABLE `edu_teacher` ( `id` char(19) NOT NULL COMMENT '讲师ID', `name` varchar(20) NOT NULL COMMENT '讲师姓名', `intro` varchar(500) NOT NULL DEFAULT '' COMMENT '讲师简介', `career` varchar(500) DEFAULT NULL COMMENT '讲师资历,一句话说明讲师', `level` int(10) unsigned NOT NULL COMMENT '头衔 1高级讲师 2首席讲师', `avatar` varchar(255) DEFAULT NULL COMMENT '讲师头像', `sort` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '排序', `is_deleted` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '逻辑删除 1(true)已删除, 0(false)未删除', `gmt_create` datetime NOT NULL COMMENT '创建时间', `gmt_modified` datetime NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='讲师'; # # Data for table "edu_teacher" # INSERT INTO `edu_teacher` VALUES ('1','张三','近年主持国家自然科学基金(6项)、江苏省重大科技成果转化项目(5项)、江苏省产学研前瞻性联合研究项目(3项)、省工业科技支撑、省高技术、省自然科学基金等省部级及其企业的主要科研项目40多个,多个项目在企业成功转化,产生了较好的经济、社会和环境效益。积极开展产学研科技合作,并与省内16家企业建立了江苏省研究生工作站,其中6家为江苏省优秀研究生工作站','高级',1,'https://guli-file-190513.oss-cn-beijing.aliyuncs.com/avatar/default.jpg',0,0,'2019-10-30 14:18:46','2019-11-12 13:36:36'),('1189389726308478977','晴天','高级讲师简介','高级讲师资历',2,'https://online-teach-file.oss-cn-beijing.aliyuncs.com/teacher/2019/10/30/de47ee9b-7fec-43c5-8173-13c5f7f689b2.png',1,0,'2019-10-30 11:53:03','2019-10-30 11:53:03'),('1189390295668469762','李刚','高级讲师简介','高级讲师',2,'https://online-teach-file.oss-cn-beijing.aliyuncs.com/teacher/2019/10/30/b8aa36a2-db50-4eca-a6e3-cc6e608355e0.png',2,0,'2019-10-30 11:55:19','2019-11-12 13:37:52'),('1189426437876985857','王二','高级讲师简介','高级讲师',1,'https://online-teach-file.oss-cn-beijing.aliyuncs.com/teacher/2019/11/08/e44a2e92-2421-4ea3-bb49-46f2ec96ef88.png',0,0,'2019-10-30 14:18:56','2019-11-12 13:37:35'),('1189426464967995393','王五','高级讲师简介','高级讲师',1,'https://online-teach-file.oss-cn-beijing.aliyuncs.com/teacher/2019/10/30/65423f14-49a9-4092-baf5-6d0ef9686a85.png',0,0,'2019-10-30 14:19:02','2019-11-12 13:37:18'),('1192249914833055746','李四','高级讲师简介','高级讲师',1,'https://online-teach-file.oss-cn-beijing.aliyuncs.com/teacher/2019/11/07/91871e25-fd83-4af6-845f-ea8d471d825d.png',0,0,'2019-11-07 09:18:25','2019-11-12 13:37:01'),('1192327476087115778','1222-12-12','1111','11',1,'https://online-teach-file.oss-cn-beijing.aliyuncs.com/teacher/2019/11/08/5805c6cd-c8ad-4a77-aafd-d2e083bfd8a4.png',0,1,'2019-11-07 14:26:37','2019-11-11 16:26:26'),('1195337453429129218','test','sdfsdf','sdfdf',1,'https://guli-file-190513.oss-cn-beijing.aliyuncs.com/avatar/default.jpg',0,1,'2019-11-15 21:47:12','2019-11-15 21:47:27');
2、数据库设计规约
以下规约只针对本模块,更全面的文档参考《阿里巴巴Jva开发手册》:五、MySQL数据库
1、库名与应用名称尽量一致
2、表名、字段名必须使用小写字母或数字,禁止出现数字开头
3、表名不使用复数名词
4、表的命名最好是加上“业务名称表的作用”。如edu_teacher
5、表必备三字段:id,gmt create,gmt modified
2.2、创建项目结构
1、创建父工程
①创建guli_parent项目(2.6.13)
②pom文件添加:
<packaging>pom</packaging>
③删除原有的dependencies
④添加依赖版本
<properties> <java.version>1.8</java.version> <guli.version>0.0.1-SNAPSHOT</guli.version> <mybatis-plus.version>3.5.2</mybatis-plus.version> <velocity.version>2.0</velocity.version> <swagger.version>2.7.0</swagger.version> <aliyun.oss.version>3.13.0</aliyun.oss.version> <jodatime.version>2.10.1</jodatime.version> <poi.version>3.17</poi.version> <commons-fileupload.version>1.3.1</commons-fileupload.version> <commons-io.version>2.6</commons-io.version> <httpclient.version>4.5.1</httpclient.version> <jwt.version>0.7.0</jwt.version> <aliyun-java-sdk-core.version>4.3.3</aliyun-java-sdk-core.version> <aliyun-sdk-oss.version>3.13.0</aliyun-sdk-oss.version> <aliyun-java-sdk-vod.version>2.15.2</aliyun-java-sdk-vod.version> <aliyun-java-vod-upload.version>1.4.15</aliyun-java-vod-upload.version> <aliyun-sdk-vod-upload.version>1.4.15</aliyun-sdk-vod-upload.version> <fastjson.version>1.2.28</fastjson.version> <gson.version>2.8.2</gson.version> <json.version>20170516</json.version> <commons-dbutils.version>1.7</commons-dbutils.version> <canal.client.version>1.1.0</canal.client.version> <docker.image.prefix>zx</docker.image.prefix> <cloud-alibaba.version>2021.0.4.0</cloud-alibaba.version> </properties>
⑤配置
<dependencyManagement> <dependencies> <!--Spring Cloud--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>${cloud-alibaba.version}</version> <type>pom</type> <scope>import</scope> </dependency> <!--mybatis-plus 持久层--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis-plus.version}</version> </dependency> <!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 --> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>${velocity.version}</version> </dependency> <!--swagger--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${swagger.version}</version> </dependency> <!--swagger ui--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${swagger.version}</version> </dependency> <!--aliyunOSS--> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>${aliyun.oss.version}</version> </dependency> <!--日期时间工具--> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>${jodatime.version}</version> </dependency> <!--xls--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>${poi.version}</version> </dependency> <!--xlsx--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>${poi.version}</version> </dependency> <!--文件上传--> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>${commons-fileupload.version}</version> </dependency> <!--commons-io--> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>${commons-io.version}</version> </dependency> <!--httpclient--> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>${httpclient.version}</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>${gson.version}</version> </dependency> <!-- JWT --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>${jwt.version}</version> </dependency> <!--aliyun--> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-core</artifactId> <version>${aliyun-java-sdk-core.version}</version> </dependency> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>${aliyun-sdk-oss.version}</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-sdk-vod</artifactId> <version>${aliyun-java-sdk-vod.version}</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-java-vod-upload</artifactId> <version>${aliyun-java-vod-upload.version}</version> </dependency> <dependency> <groupId>com.aliyun</groupId> <artifactId>aliyun-sdk-vod-upload</artifactId> <version>${aliyun-sdk-vod-upload.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${fastjson.version}</version> </dependency> <dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> <version>${json.version}</version> </dependency> <dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>${commons-dbutils.version}</version> </dependency> <dependency> <groupId>com.alibaba.otter</groupId> <artifactId>canal.client</artifactId> <version>${canal.client.version}</version> </dependency> </dependencies> </dependencyManagement>
aliyun-java-vod-upload引入失败解决_初栀_的博客-CSDN博客
⑥删除父工程的src文件夹
2、新建子模块service
①pom文件
添加:
<packaging>pom</packaging>
②添加子模块需要的依赖
<dependencies> <!--<dependency> <groupId>com.lxg</groupId> <artifactId>service_base</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>--> <!-- <dependency>--> <!-- <groupId>org.springframework.cloud</groupId>--> <!-- <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>--> <!-- </dependency>--> <!--hystrix依赖,主要是用 @HystrixCommand --> <!-- <dependency>--> <!-- <groupId>org.springframework.cloud</groupId>--> <!-- <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>--> <!-- </dependency>--> <!--服务注册--> <!-- <dependency>--> <!-- <groupId>com.alibaba.cloud</groupId>--> <!-- <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>--> <!-- </dependency>--> <!--服务调用--> <!-- <dependency>--> <!-- <groupId>org.springframework.cloud</groupId>--> <!-- <artifactId>spring-cloud-starter-openfeign</artifactId>--> <!-- </dependency>--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 --> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> </dependency> <!--swagger--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> </dependency> <!--lombok用来简化实体类:需要安装lombok插件--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--xls--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> </dependency> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> </dependency> <!--httpclient--> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency> <!--commons-io--> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> </dependency> <!--gson--> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> </dependency> </dependencies>
③删除src文件夹
3、继续在service模块下新建子模块service-edu
①添加application.properties
# 服务端口 server.port=8001 # 服务名 spring.application.name=service-edu # 环境设置:dev、test、prod spring.profiles.active=dev # mysql数据库连接 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=123456 #返回json的全局时间格式 spring.jackson.date-format=yyyy-MM-dd HH:mm:ss spring.jackson.time-zone=GMT+8 #mybatis日志 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
②用代码生成器生成相关代码
旧版本:
package com.example.demo; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.GlobalConfig; import com.baomidou.mybatisplus.generator.config.PackageConfig; import com.baomidou.mybatisplus.generator.config.StrategyConfig; import com.baomidou.mybatisplus.generator.config.rules.DateType; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import org.junit.Test; /** * @author * @since 2018/12/13 */ public class CodeGenerator { @Test public void run() { // 1、创建代码生成器 AutoGenerator mpg = new AutoGenerator(); // 2、全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); gc.setOutputDir(projectPath + "/src/main/java"); gc.setAuthor("testjava"); gc.setOpen(false); //生成后是否打开资源管理器 gc.setFileOverride(false); //重新生成时文件是否覆盖 gc.setServiceName("%sService"); //去掉Service接口的首字母I gc.setIdType(IdType.ID_WORKER); //主键策略 gc.setDateType(DateType.ONLY_DATE);//定义生成的实体类中日期类型 gc.setSwagger2(true);//开启Swagger2模式 mpg.setGlobalConfig(gc); // 3、数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://localhost:3306/guli"); dsc.setDriverName("com.mysql.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("root"); dsc.setDbType(DbType.MYSQL); mpg.setDataSource(dsc); // 4、包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName("edu"); //模块名 pc.setParent("com.example.demo"); pc.setController("controller"); pc.setEntity("entity"); pc.setService("service"); pc.setMapper("mapper"); mpg.setPackageInfo(pc); // 5、策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setInclude("edu_teacher"); strategy.setNaming(NamingStrategy.underline_to_camel);//数据库表映射到实体的命名策略 strategy.setTablePrefix(pc.getModuleName() + "_"); //生成实体时去掉表前缀 strategy.setColumnNaming(NamingStrategy.underline_to_camel);//数据库表字段映射到实体的命名策略 strategy.setEntityLombokModel(true); // lombok 模型 @Accessors(chain = true) setter链式操作 strategy.setRestControllerStyle(true); //restful api风格控制器 strategy.setControllerMappingHyphenStyle(true); //url中驼峰转连字符 mpg.setStrategy(strategy); // 6、执行 mpg.execute(); } }
新版本
package com.lxg.demo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.config.OutputFile; import com.baomidou.mybatisplus.generator.config.po.LikeTable; import com.baomidou.mybatisplus.generator.config.rules.DateType; import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import java.util.Collections; public class CodeGenerator { public static void main(String[] args) { FastAutoGenerator.create("jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8", "root", "123456") // 全局配置 .globalConfig(builder -> { builder.author("xiaolin") // 设置作者 .commentDate("yyyy-MM-dd hh:mm:ss") //注释日期 .enableSwagger() // 开启 swagger 模式 // .enableKotlin() //开启 kotlin 模式 // .fileOverride() // 覆盖已生成文件 // .dateType(DateType.TIME_PACK) //时间策略 .outputDir( "E:\\一些笔记\\项目实战\\谷粒学院\\code\\guli_parent\\service\\service_edu\\src\\main\\java") // 指定输出目录 .disableOpenDir() //禁止打开输出目录,默认打开 ; }) // 包配置 .packageConfig(builder -> { builder.parent("com.lxg") // 设置父包名 .moduleName("eduservice") // 设置父包模块名 .controller("controller") // 设置 controller 包名 .entity("entity") // 设置 entity 包名 .service("service") // 设置 service 包名 .serviceImpl("service.impl") // 设置 serviceImpl 包名 .mapper("mapper") // 设置 mapper 包名 .xml("mapper.xml") // 设置 xml 包名 .other("com.lxg.demo") // 设置自定义文件包名 .pathInfo(Collections.singletonMap(OutputFile.xml, "E:\\一些笔记\\项目实战\\谷粒学院\\code\\guli_parent\\service\\service_edu\\src\\main\\resources\\mapper")); // 设置mapperXml生成路径 }) // 策略配置 .strategyConfig(builder -> { builder.addInclude("edu_teacher") // 设置需要生成的表名 include 与 exclude 只能配置一项 // .addExclude("test") // 设置需要排除的表名 include 与 exclude 只能配置一项 .addTablePrefix("edu_") // 设置过滤表前缀 // .addFieldSuffix("_") // 设置字段后缀 // .addTableSuffix("_") // 设置表后缀 // .addFieldPrefix("_") // 设置字段前缀 // .enableCapitalMode() // 开启全局大写命名 // .enableSkipView() // 开启跳过视图 // .disableSqlFilter() // 禁用sql过滤 // .enableSchema() // 开启 schema // .likeTable(new LikeTable("USER")) //表名生成策略:模糊匹配表名,如表名包含 USER 的均会生成 likeTable 与 notLikeTable 只能配置一项 // .notLikeTable(new LikeTable("test")) //表名生成策略:模糊排除表名 // Entity 策略配置 .entityBuilder() .enableLombok() //开启 Lombok .fileOverride() // 覆盖已生成文件 .naming(NamingStrategy.underline_to_camel) //数据库表映射到实体的命名策略:下划线转驼峰命 .columnNaming(NamingStrategy.underline_to_camel) //数据库表字段映射到实体的命名策略:下划线转驼峰命 .idType(IdType.AUTO) // Mapper 策略配置 .mapperBuilder() .fileOverride() // 覆盖已生成文件 // Service 策略配置 .serviceBuilder() .fileOverride() // 覆盖已生成文件 .formatServiceFileName("%sService") //格式化 service 接口文件名称,%s进行匹配表名,如 UserService .formatServiceImplFileName("%sServiceImpl") //格式化 service 实现类文件名称,%s进行匹配表名,如 UserServiceImpl // Controller 策略配置 .controllerBuilder() .fileOverride() // 覆盖已生成文件 ; }) .execute(); } }
详情配置前往官网:
代码生成器配置新 | MyBatis-Plus (baomidou.com)
以上配置过于繁琐可以选择使用mybatis-x一键生成!
2.3、创建controller
@RestController @RequestMapping("/eduservice/teacher") public class TeacherController { //把service注入 @Autowired private TeacherService teacherService; //1.查询讲师表所有数据 //rest风格 @RequestMapping("/findAll") public List<Teacher> findAll() { //调用service的方法实现查询所有的操作 //在service中注入mapper,调用mapper的方法 List<Teacher> list = teacherService.list(null); return list; } }
2.4、创建工程启动类
@SpringBootApplication public class EduApplication { public static void main(String[] args) { SpringApplication.run(EduApplication.class,args); } }
2.5、创建配置类,配置mapper扫描和其他配置
package com.lxg.eduservice.config; import com.baomidou.mybatisplus.core.injector.ISqlInjector; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @auther xiaolin * @creatr 2023/5/8 0:13 */ @Configuration @MapperScan("com.lxg.eduservice.mapper") public class EduConfig { }
2.6、测试
启动项目
http://localhost:8001/eduservice/teacher/findAll
2.7、讲师逻辑删除功能
1、配置逻辑删除插件
旧版才需要
//逻辑删除插件 /** * 旧版 */ /*@Bean public ISqlInjector sqlInjector() { return new LogicSqlInjector(); }*/
2、配置文件
mapbatis-plus-global-config.db-config.logic-delete-value=1 mapbatis-plus-global-config.db-config.logic-not-delete-value=0
3、controller添加删除方法
//2.逻辑删除讲师的方法 @DeleteMapping("{id}")//id值需要通过路径传递 public boolean removeTeacher(@PathVariable String id) { boolean flag = teacherService.removeById(id); return flag; }
2.8、整合Swagger
1、Swagger2介绍
前后端分离开发模式中,api文档是最好的沟通方式。
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。
-
及时性(接口变更后,能够及时准确地通知相关前后端开发人员)
-
规范性(并且保证接口的规范性,如接口的地址,请求方式,参数及响应格式和错误信息)
-
一致性(接口信息一致,不会出现因开发人员拿到的文档版本不一致,而出现分歧)
-
可测性(直接在接口文档上进行测试,以方便理解业务)
2、抽取公共模块
由于其他模块也需要配置swagger2
①创建common模块(父工程下)
②引入相关依赖
<packaging>pom</packaging> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <scope>provided </scope> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <scope>provided </scope> </dependency> <!--lombok用来简化实体类:需要安装lombok插件--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided </scope> </dependency> <!--swagger--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <scope>provided </scope> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <scope>provided </scope> </dependency> <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- spring2.X集成redis所需common-pool2 <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.0</version> </dependency>--> </dependencies>
③在common下创建子模块service_base
④创建swagger的配置类
package com.lxg.servicebase; import com.google.common.base.Predicates; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.service.Contact; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; /** * @auther xiaolin * @creatr 2023/5/8 10:54 */ @Configuration //配置类 @EnableSwagger2 //swagger注解 public class SwaggerConfig { @Bean public Docket webApiConfig(){ return new Docket(DocumentationType.SWAGGER_2) .groupName("webApi") .apiInfo(webApiInfo()) .select() .paths(Predicates.not(PathSelectors.regex("/admin/.*"))) .paths(Predicates.not(PathSelectors.regex("/error.*"))) .build(); } private ApiInfo webApiInfo(){ return new ApiInfoBuilder() .title("网站-课程中心API文档") .description("本文档描述了课程中心微服务接口定义") .version("1.0") .contact(new Contact("lxg", "http://www.lxg.com", "1849569695@qq.com")) .build(); } }
3、在service模块中引入service_base依赖
<dependency> <groupId>com.lxg</groupId> <artifactId>service_base</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
4、在service-edu启动类上添加注解,进行测试
@ComponentScan(basePackages = {"com.lxg"})
Spring Boot 启动类默认会扫描其所在包及其子包下的所有类,寻找被 @Component
、@Service
、@Repository
、@Controller
、@RestController
、@Configuration
注解的类,并将其作为 Bean 注入到 Spring 容器中。
如果需要扫描其他包下的类,可以使用 @ComponentScan
注解指定要扫描的包路径。
示例:
@SpringBootApplication @ComponentScan(basePackages = {"com.example.controller", "com.example.service"}) public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
上面的示例中,@SpringBootApplication
注解默认会扫描 MyApplication
类所在的包及其子包下的所有类,而 @ComponentScan
注解指定了要扫描的 com.example.controller
和 com.example.service
包下的类。
5、定义接口说明和参数说明
package com.lxg.eduservice.controller; import com.lxg.eduservice.entity.Teacher; import com.lxg.eduservice.service.TeacherService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; /** * <p> * 讲师 前端控制器 * </p> * * @author xiaolin * @since 2023-05-07 06:07:57 */ @Api(tags="讲师管理") @RestController @RequestMapping("/eduservice/teacher") public class TeacherController { //http://localhost:8001/eduservice/teacher/findAll //把service注入 @Autowired private TeacherService teacherService; //1.查询讲师表所有数据 //rest风格 @ApiOperation(value="所有讲师列表") @GetMapping("/findAll") public List<Teacher> findAllTeacher() { //调用service的方法实现查询所有的操作 //在service中注入mapper,调用mapper的方法 List<Teacher> list = teacherService.list(null); return list; } //2.逻辑删除讲师的方法 @ApiOperation(value="根据ID删除讲师") @DeleteMapping("{id}")//id值需要通过路径传递 public boolean removeTeacher(@PathVariable String id) { boolean flag = teacherService.removeById(id); return flag; } }
定义在类上:@Api
定义在方法上:@ApiOperation
定义在参数上:@ApiParam
6、测试效果
启动时遇到的问题:
Spring boot 2.6使用Swagger2提示“Cannot invoke “org.springframework.web.servlet.mvc.condition.Patterns”错误
添加配置即可
spring.mvc.pathmatch.matching-strategy=ant-path-matcher
2.9、统一返回数据格式
项目中我们会将响应封装成json返回,一般我们会将所有接口的数据格式统一,使前端(iOS Android,
Web)对数据的操作更一致、轻松。
一般情况下,统一返回数据格式没有固定的格式,只要能描述清楚返回的数据状态以及要返回的具体数
据就可以。但是一般会包含状态码、返回消息、数据这几部分内容
例如,我们的系统要求返回的基本数据格式如下:
列表:
{ "success": true, "code": 20000, "message": "成功", "data": { "items": [ { "id": "1" "name": "刘德华" "intro": "毕业于师范大学数学系,热爱教育事业,执教数学思维6年有余" } ] } }
分页:
{ "success":true, "code":20000, "message":"成功", "data": { "tota1":17, "rows": [ { "id":"1", "name":"刘德华", "intro'":"毕业于师范大学数学系,热爱教育事业,执教数学思维6年有余" } ] } }
没有返回数据:
{ "success": true, "code": 20000, "message": "成功", "data": {} }
失败:
{ "success": false, "code": 20001, "message": "失败", "data": {} }
因此,我们定义统一结果
{ "success": 布尔值, //响应是否成功 "code": 数字, //响应码 "message": 字符串, //返回消息 "data": HashMap //返回数据,放在键值对中 }
1、在common模块下创建子模块common_utils
2、创建interface,定义数据返回状态码
package com.lxg.commonutils; /** * @auther xiaolin * @creatr 2023/5/8 19:44 */ public interface ResultCode { public static Integer SUCCESS = 20000;//成功 public static Integer ERROR = 20001;//失败 }
或定义枚举类
package com.lxg.commonutils; public enum ErrorCode { SUCCESS(20000, "成功"), ERROR(20001, "失败"); private int code; private String message; ErrorCode(int code, String message) { this.code = code; this.message = message; } public int getCode() { return code; } public String getMessage() { return message; }; }
3、创建结果类
package com.lxg.commonutils; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.experimental.Accessors; import java.util.HashMap; import java.util.Map; /** * @auther xiaolin * @creatr 2023/5/8 19:51 */ //统一返回结果的类 @Data //@Accessors(chain = true) public class R { @ApiModelProperty(value = "是否成功") private Boolean success; @ApiModelProperty(value = "返回码") private Integer code; @ApiModelProperty(value = "返回消息") private String message; @ApiModelProperty(value = "返回数据") private Map<String,Object> data = new HashMap<>(); //把构造方法私有 private R(){} //成功静态方法 public static R ok(){ R r = new R(); r.setSuccess(true); r.setCode(RCode.SUCCESS.getCode()); r.setMessage(RCode.SUCCESS.getMessage()); return r; } //失败静态方法 public static R error(){ R r = new R(); r.setSuccess(false); r.setCode(RCode.ERROR.getCode()); r.setMessage(RCode.ERROR.getMessage()); return r; } //链式编程 public R success(Boolean success){ this.setSuccess(success); return this; } public R message(String message){ this.setMessage(message); return this; } public R code(Integer code){ this.setCode(code); return this; } public R data(String key,Object value){ this.data.put(key,value); return this; } public R data(Map<String,Object> map){ this.setData(map); return this; } }
4、在service中引入common_utils
<dependency> <groupId>com.lxg</groupId> <artifactId>common_utils</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
5、修改接口方法的返回值都为R
package com.lxg.eduservice.controller; import com.lxg.commonutils.R; import com.lxg.eduservice.entity.Teacher; import com.lxg.eduservice.service.TeacherService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; /** * <p> * 讲师 前端控制器 * </p> * * @author xiaolin * @since 2023-05-07 06:07:57 */ @Api(tags="讲师管理") @RestController @RequestMapping("/eduservice/teacher") public class TeacherController { //http://localhost:8001/eduservice/teacher/findAll //把service注入 @Autowired private TeacherService teacherService; //1.查询讲师表所有数据 //rest风格 @ApiOperation(value="所有讲师列表") @GetMapping("/findAll") public R findAllTeacher() { //调用service的方法实现查询所有的操作 //在service中注入mapper,调用mapper的方法 List<Teacher> list = teacherService.list(null); return R.ok().data("items", list); } //2.逻辑删除讲师的方法 @ApiOperation(value="根据ID删除讲师") @DeleteMapping("{id}")//id值需要通过路径传递 public R removeTeacher(@PathVariable String id) { boolean flag = teacherService.removeById(id); return flag ? R.ok() : R.error(); } }
6、测试
2.10、配置分页插件
1、EduConfig新增方法
//分页插件 @Bean public MybatisPlusInterceptor mpInterceptor() { //1.定义Mp拦截器 MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor(); //2.添加具体的拦截器 mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return mpInterceptor; }
2、分页查询讲师
//3.分页查询讲师的方法 //current当前页 limit每页记录数 @ApiOperation(value="分页讲师列表") @GetMapping("/pageTeacher/{current}/{limit}") public R pageListTeacher(@PathVariable long current, @PathVariable long limit) { //创建page对象 //传递两个参数:当前页 和 每页记录数 Page<Teacher> pageTeacher = new Page<>(current, limit); //调用方法时,底层封装,把分页所有数据封装到page对象里面 teacherService.page(pageTeacher, null); //把page对象里面分页的数据获取出来,交给前端使用 //pageTeacher对象里面,有我们分页的所有数据 long total = pageTeacher.getTotal();//总记录数 List<Teacher> records = pageTeacher.getRecords();//数据list集合 // Map<String, Object> map = new HashMap<>(); // map.put("total", total); // map.put("rows", records); // return R.ok().data(map); return R.ok().data("total", total).data("rows", records); }
也可以直接把page对象封装到R里,传给前端自行使用
2.11、条件查询分页
1、把条件值封装到对象中
package com.lxg.eduservice.entity.vo; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * @auther xiaolin * @creatr 2023/5/8 20:46 */ @Data public class TeacherQuery { @ApiModelProperty(value = "教师名称,模糊查询") private String name; @ApiModelProperty(value = "头衔 1高级讲师 2首席讲师") private Integer level; @ApiModelProperty(value = "查询开始时间",example = "2020-01-01 10:10:10") private String begin; //注意,这里使用的是String类型,前端传过来的数据无需进行类型转换 @ApiModelProperty(value = "查询结束时间",example = "2020-12-01 10:10:10") private String end; }
2、controller新增条件分页查询方法
//4.条件查询带分页的方法 @ApiOperation(value="条件查询带分页的方法") @PostMapping("/pageTeacherCondition/{current}/{limit}") public R pageTeacherCondition(@PathVariable long current, @PathVariable long limit,@RequestBody(required = false) TeacherQuery teacherQuery) { //创建page对象 Page<Teacher> pageTeacher = new Page<>(current, limit); //构建条件 LambdaQueryWrapper<Teacher> wrapper = new LambdaQueryWrapper<>(); //多条件组合查询 //mybatis学过 动态sql String name = teacherQuery.getName(); Integer level = teacherQuery.getLevel(); String begin = teacherQuery.getBegin(); String end = teacherQuery.getEnd(); System.out.println("===================================="+begin); //判断条件值是否为空,如果不为空拼接条件 String s = String.valueOf(level); if (s.equals("null")){ s = ""; } wrapper.like(StringUtils.hasLength(name), Teacher::getName, name); wrapper.eq(StringUtils.hasLength(s), Teacher::getLevel, level); wrapper.ge(StringUtils.hasLength(begin), Teacher::getGmtCreate, begin); wrapper.le(StringUtils.hasLength(end), Teacher::getGmtCreate, end); //调用方法实现条件查询分页 teacherService.page(pageTeacher, wrapper); long total = pageTeacher.getTotal();//总记录数 List<Teacher> records = pageTeacher.getRecords();//数据list集合 return R.ok().data("total", total).data("rows", records); }
3、测试
2.12、讲师添加功能
1、在实体类添加自动填充注解
@ApiModelProperty("创建时间") @TableField(fill = FieldFill.INSERT) private Date gmtCreate; @ApiModelProperty("更新时间") @TableField(fill = FieldFill.INSERT_UPDATE) private Date gmtModified;
2、在service_base模块中添加配置类
package com.lxg.servicebase.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.util.Date; /** * @auther xiaolin * @creatr 2023/5/8 23:46 */ @Component public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { this.setFieldValByName("gmtCreate",new Date(),metaObject); this.setFieldValByName("gmtModified",new Date(),metaObject); } @Override public void updateFill(MetaObject metaObject) { this.setFieldValByName("gmtModified",new Date(),metaObject); } }
3、编写controller添加方法
//5.添加讲师接口的方法 @ApiOperation(value="添加讲师接口的方法") @PostMapping("/addTeacher") public R addTeacher(@RequestBody Teacher teacher) { boolean save = teacherService.save(teacher); return save ? R.ok() : R.error(); }
4、注意实体类id
@TableId(value = "id", type = IdType.ASSIGN_ID)
使用雪花算法
5、测试
2.13、讲师修改功能
//7.讲师修改功能 @ApiOperation(value="讲师修改功能") @PostMapping("/updateTeacher") public R updateTeacher(@RequestBody Teacher teacher) { boolean flag = teacherService.updateById(teacher); return flag ? R.ok() : R.error(); }
2.14、根据讲师id进行查询
//6.根据讲师id进行查询 @ApiOperation(value="根据讲师id进行查询") @GetMapping("/getTeacher/{id}") public R getTeacher(@PathVariable String id) { Teacher teacher = teacherService.getById(id); return R.ok().data("teacher", teacher); }
2.15、统一异常处理
1、制造异常
int a = 10/0;
2、什么是统一异常处理
我们想让异常结果也显示为统一的返回结果对象,并且统一处理系统的异常信息,那么需要统一异常处理
3、在service_base中创建全局异常处理类
package com.lxg.servicebase.exceptionhandler; import com.lxg.commonutils.R; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; /** * @auther xiaolin * @creatr 2023/5/9 9:39 */ @ControllerAdvice public class GlobalExceptionHandler { //指定出现什么异常执行这个方法 @ExceptionHandler(Exception.class) @ResponseBody //为了返回数据 public R error(Exception e){ e.printStackTrace(); return R.error().message("执行了全局异常处理"); } }
4、service_base中引入common_utils依赖
5、删除service中的common_utils依赖
6、测试
三、第三天
1、统一异常处理
1.1、全局异常
//指定出现什么异常执行这个方法 @ExceptionHandler(Exception.class) @ResponseBody //为了返回数据 public R error(Exception e){ e.printStackTrace(); return R.error().message("执行了全局异常处理"); }
1.2、特定异常
//指定出现什么异常执行这个方法 @ExceptionHandler(ArithmeticException.class) @ResponseBody //为了返回数据 public R error(ArithmeticException e){ e.printStackTrace(); return R.error().message("执行了ArithmeticException异常处理"); }
1.3、自定义异常
1、创建自定义异常类
package com.lxg.servicebase.exceptionhandler; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /** * @auther xiaolin * @creatr 2023/5/9 10:50 */ @Data @AllArgsConstructor // 有参构造 @NoArgsConstructor // 无参构造 public class GuliException extends RuntimeException{ private Integer code;//状态码 private String msg;//异常信息 }
2、新增自定义异常处理方法
//自定义异常 @ExceptionHandler(GuliException.class) @ResponseBody //为了返回数据 public R error(GuliException e){ e.printStackTrace(); return R.error().code(e.getCode()).message(e.getMsg()); }
3、抛出自定义异常
try { int i = 1/0; } catch (Exception e) { //执行自定义异常 throw new GuliException(20001, "执行了自定义异常处理"); }
4、测试
2、统一日志管理
2.1、配置日志级别
日志记录器(Logger)的行为是分等级的。如下表所示:
分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL
默认情况下,spring boot从控制台打印出来的日志级别只有INFO及以上级别,可以配置日志级别
#设置日志级别 logging.level.root=WARN
这种方式只能将日志打印在控制台上
2.2、Logback日志
spring boot内部使用Logback作为日志实现的框架。
Logback和log4j非常相似,如果你对log4j很熟悉,那对logback很快就会得心应手。
logback相对于log4j的一些优点:https://blog.csdn.net/caisini_vc/article/details/48551287
1、删除application.properties中的日志配置
2、resources中创建logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration scan="true" scanPeriod="10 seconds"> <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 --> <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true --> <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 --> <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --> <contextName>logback</contextName> <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 --> <property name="log.path" value="D:/guli_1010/edu" /> <!-- 彩色日志 --> <!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 --> <!-- magenta:洋红 --> <!-- boldMagenta:粗红--> <!-- cyan:青色 --> <!-- white:白色 --> <!-- magenta:洋红 --> <property name="CONSOLE_LOG_PATTERN" value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/> <!--输出到控制台--> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息--> <!-- 例如:如果此处配置了INFO级别,则后面其他位置即使配置了DEBUG级别的日志,也不会被输出 --> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>INFO</level> </filter> <encoder> <Pattern>${CONSOLE_LOG_PATTERN}</Pattern> <!-- 设置字符集 --> <charset>UTF-8</charset> </encoder> </appender> <!--输出到文件--> <!-- 时间滚动输出 level为 INFO 日志 --> <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在记录的日志文件的路径及文件名 --> <file>${log.path}/log_info.log</file> <!--日志文件输出格式--> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> </encoder> <!-- 日志记录器的滚动策略,按日期,按大小记录 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 每天日志归档路径以及格式 --> <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--日志文件保留天数--> <maxHistory>15</maxHistory> </rollingPolicy> <!-- 此日志文件只记录info级别的 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>INFO</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- 时间滚动输出 level为 WARN 日志 --> <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在记录的日志文件的路径及文件名 --> <file>${log.path}/log_warn.log</file> <!--日志文件输出格式--> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> <!-- 此处设置字符集 --> </encoder> <!-- 日志记录器的滚动策略,按日期,按大小记录 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--日志文件保留天数--> <maxHistory>15</maxHistory> </rollingPolicy> <!-- 此日志文件只记录warn级别的 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>warn</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- 时间滚动输出 level为 ERROR 日志 --> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在记录的日志文件的路径及文件名 --> <file>${log.path}/log_error.log</file> <!--日志文件输出格式--> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> <!-- 此处设置字符集 --> </encoder> <!-- 日志记录器的滚动策略,按日期,按大小记录 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--日志文件保留天数--> <maxHistory>15</maxHistory> </rollingPolicy> <!-- 此日志文件只记录ERROR级别的 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <!-- <logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。 <logger>仅有一个name属性, 一个可选的level和一个可选的addtivity属性。 name:用来指定受此logger约束的某一个包或者具体的某一个类。 level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF, 如果未设置此属性,那么当前logger将会继承上级的级别。 --> <!-- 使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作: 第一种把<root level="INFO">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息 第二种就是单独给mapper下目录配置DEBUG模式,代码如下,这样配置sql语句会打印,其他还是正常DEBUG级别: --> <!--开发环境:打印控制台--> <springProfile name="dev"> <!--可以输出项目中的debug日志,包括mybatis的sql日志--> <logger name="com.guli" level="INFO" /> <!-- root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性 level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,默认是DEBUG 可以包含零个或多个appender元素。 --> <root level="INFO"> <appender-ref ref="CONSOLE" /> <appender-ref ref="INFO_FILE" /> <appender-ref ref="WARN_FILE" /> <appender-ref ref="ERROR_FILE" /> </root> </springProfile> <!--生产环境:输出到文件--> <springProfile name="pro"> <root level="INFO"> <appender-ref ref="CONSOLE" /> <appender-ref ref="DEBUG_FILE" /> <appender-ref ref="INFO_FILE" /> <appender-ref ref="ERROR_FILE" /> <appender-ref ref="WARN_FILE" /> </root> </springProfile> </configuration>
3、将异常信息输出到文件中
4、将日志堆栈信息输出到文件
定义工具类:
package com.guli.common.util; public class ExceptionUtil { public static String getMessage(Exception e) { StringWriter sw = null; PrintWriter pw = null; try { sw = new StringWriter(); pw = new PrintWriter(sw); // 将出错的栈信息输出到printWriter中 e.printStackTrace(pw); pw.flush(); sw.flush(); } finally { if (sw != null) { try { sw.close(); } catch (IOException e1) { e1.printStackTrace(); } } if (pw != null) { pw.close(); } } return sw.toString(); } }
GuliException中创建toString方法
@Override public String toString(){ return "GuliException{" + "message='" + this.getMessage() + "', code=" + code + '}'; }
调用:
log.error(ExceptionUtil.getMessage(e));
3、前端开发
前端工程师“Front-End-Developer”源自于美国。大约从2005年开始正式的前端工程师角色被行业所认可,到了2010年,互联网开始全面进入移动时代,前端开发的工作越来越重要。
最初所有的开发工作都是由后端工程师完成的,随着业务越来越繁杂,工作量变大,于是我们将项目中的可视化部分和一部分交互功能的开发工作剥离出来,形成了前端开发。
由于互联网行业的急速发展,导致了在不同的国家,有着截然不同的分工体制。
在日本和一些人口比较稀疏的国家,例如加拿大、澳洲等,流行“Full-Stack Engineer”,也就是我们通常所说的全栈工程师。通俗点说就是一个人除了完成前端开发和后端开发工作以外,有的公司从产品设计到项目开发再到后期运维可能都是同一个人,甚至可能还要负责UI、配动画,也可以是扫地、擦窗、写文档、维修桌椅等等。
而在美国等互联网环境比较发达的国家项目开发的分工协作更为明确,整个项目开发分为前端、中间层和后端三个开发阶段,这三个阶段分别由三个或者更多的人来协同完成。
国内的大部分互联网公司只有前端工程师和后端工程师,中间层的工作有的由前端来完成,有的由后端来完成。
PRD(产品原型-产品经理) - PSD(视觉设计-UI工程师) - HTML/CSS/JavaScript(PC/移动端网页,实现网页端的视觉展示和交互-前端工程师)
3.1、安装vscode
1、下载地址
https://code.visualstudio.com/
2、中文界面设置
-
首先安装中文插件:Chinese (Simplified) Language Pack for Visual Studio Code
-
右下角弹出是否重启vs,点击“yes”
-
有些机器重启后如果界面没有变化,则 点击 左边栏Manage -> Command Paletet...【Ctrl+Shift+p】
-
在搜索框中输入“configure display language”,回车
-
打开locale.json文件,修改文件下的属性 "locale":"zh-cn"
{ // 定义 VS Code 的显示语言。 // 请参阅 https://go.microsoft.com/fwlink/?LinkId=761051,了解支持的语言列表。 "locale":"zh-cn" // 更改将在重新启动 VS Code 之后生效。 } -
重启vs
3、安装插件
另外安装:
Live Server、Live-preview、open in brower、Vetur、vue-helper
4、创建工作区
1、创建空文件夹
2、vs中打开空文件夹
3、文件——另存为工作区
5、创建文件夹和网页
6、预览网页
7、设置字体大小
左边栏Manage -> settings -> 搜索 “font” -> Font size
8、开启完整的Emmet语法支持
Emmet语法是一种用于快速生成HTML和CSS代码的缩写语法。它可以通过简单的语法,快速生成复杂的HTML和CSS代码。Emmet语法支持多种编辑器和IDE,例如Sublime Text、Visual Studio Code、Atom等,使开发者可以更快地编写代码。Emmet语法使用简单的缩写表示HTML标签和CSS属性,例如div#header>ul.nav>li*4>a
可以生成一个包含4个链接的导航栏。
设置中搜索 Emmet:启用如下选项,必要时重启vs
3.2、ES6复习
1、简介
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
ECMAScript 和 JavaScrip的关系:
一个常见的问题是,ECMAScript 和 JavaScript 到底是什么关系?
要讲清楚这个问题,需要回顾历史。1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。
因此,ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript方言还有 Jscript 和 ActionScript)
ES6 与 ECMAScript 2015 的关系:
ECMAScript 2015(简称 ES2015)这个词,也是经常可以看到的。它与 ES6 是什么关系呢?
2011 年,ECMAScript 5.1 版发布后,就开始制定 6.0 版了。因此,ES6 这个词的原意,就是指 JavaScript 语言的下一个版本。
ES6 的第一个版本,在 2015 年 6 月发布,正式名称是《ECMAScript 2015 标准》(简称 ES2015)。
2016 年 6 月,小幅修订的《ECMAScript 2016 标准》(简称 ES2016)如期发布,这个版本可以看作是 ES6.1版,因为两者的差异非常小,基本上是同一个标准。根据计划,2017 年 6 月发布 ES2017 标准。
因此,ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了ES2015、ES2016、ES2017 等等,而ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”。
2、基本语法
ES标准中不包含 DOM 和 BOM的定义,只涵盖基本数据类型、关键字、语句、运算符、内建对象、内建函数等通用语法。
本部分只学习前端开发中ES6的最少必要知识,方便后面项目开发中对代码的理解。
①let
let变量作用范围
<!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>Document</title> </head> <script> //es6如何定义变量,定义变量特点 //js 定义:var a = 1; //es6 定义变量,使用let关键字 let a = 1; //let定义变量有作用范围 //1 创建代码块,定义变量 { var a = 10; let b = 20; } //2 在代码块 外面输出 console.log(a); // console.log(b); //Uncaught ReferenceError: b is not defined </script> <body> </body> </html>
let定义变量特点
<!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>Document</title> </head> <script> //var可以声明多次 //1et只能声明一次 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 变量提升 console.log(a); var a = 2; // 1、let声明变量,没有变量提升 console.log(b);//报错 let b = 10; </script> <body> </body> </html>
②const
<script> //定义常量 const PI = "3.1415926"; //常量值一旦定义,不能改变 // PI = 3.14; // TypeError: Assignment to constant variable. //定义变量必须初始化 // const AA;//'const' declarations must be initialized //不会变量提升 // console.log(Max);//报错 const Max = 30; //有块级作用域 if (1 === 1) { const Max = 30; } // console.log(Max); //报错 const person = { name: 'xiaolin' }; person.name = 'XIAOLIN'; //可以修改属性 // 不能修改整个对象 /* person ={ age:20 }; console.log(person.name); */ //如果真的想将对象冻结,应该使用Object.freeze方法。 const foo = Object.freeze({}); // 常规模式时,下面一行不起作用; // 严格模式时,该行会报错 foo.prop = 123; //除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。 var constantize = (obj) => { Object.freeze(obj); Object.keys(obj).forEach((key, i) => { if (typeof obj[key] === 'object') { constantize(obj[key]); } }); } </script>
③解构赋值
解构赋值是对赋值运算符的扩展。
他是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。
数组结构赋值:
<script> //1、数组解构 // 传统 // let a = 1, b = 2, c = 3 // console.log(a, b, c) // ES6 let [x, y, z] = [1, 2, 3] console.log(x, y, z) // 对数组解构 let arr = [1, 2, 3]; let [a,b] = arr; console.log(a,b); // 可嵌套 // let [a, [b, b1, b2], c] = [1, [2, 3, 4], 3]; // console.log(a, b, b1, b2, c); </script>
对象解构赋值:
<script> //2、对象解构 let user = { name: 'Helen', age: 18 } // 传统 let name1 = user.name let age1 = user.age console.log(name1, age1) // ES6 // let { name, age } = user//注意:结构的变量必须是user中的属性 // console.log(name, age) // 解构赋值是对赋值运算符的一种扩展 // 针对数组和对象进行操作的 // 优点:代码书写上简洁易读 //如果解构不成功,变量的值就等于undefined。 let node = { type:'iden', name:'foo' } /* let type =node.type; let name = node.name; */ // 完全解构 let {type,name}=node; // console.log(type,name); /* let obj={ a:{ name:"张三" }, b:[1,2,3], c:'hello es6' } */ // 不完全解构 // let {a} = obj; // console.log(a); // 剩余运算符 //如果原来是对象:产生的就是对象;如果原来是数组,产生的就是数组 // let {a,...res}=obj; // console.log(res); //注意:对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值 //如果变量名与属性名不一致,必须写成下面这样。 let { foo: baz } = { foo: 'aaa', bar: 'bbb' }; // baz "aaa" let obj = { first: 'hello', last: 'world' }; let { first: f, last: l } = obj; //f 'hello' //l 'world' // 运行指定默认值 // let {a,b=30} = {a:20}; </script>
④模板字符串
<script> // 1、多行字符串 let string1 = `Hey, can you stop angry now?` console.log(string1) // Hey, // can you stop angry now? // 2、字符串插入变量和表达式。变量名写在 ${} 中,${} 中可以放入 JavaScript 表达式。 let name = "Mike" let age = 27 let info = `My Name is ${name},I am ${age + 1} years old next year.` console.log(info) // My Name is Mike,I am 28 years old next year. // 3、字符串中调用函数 function f() { return "have fun!" } let string2 = `Game start,${f()}` console.log(string2); // Game start,have fun! </script>
⑤声明对象简写
<script> const age = 12 const name = "Amy" // 传统 const person1 = { age: age, name: name } console.log(person1) // ES6 const person2 = { age, name } console.log(person2) //{age: 12, name: "Amy"} </script>
⑥声明方法简写
<script> // 传统 const person1 = { sayHi: function () { console.log("Hi") } } person1.sayHi();//"Hi" // ES6 const person2 = { sayHi() { console.log("Hi") } } person2.sayHi() //"Hi" </script>
⑦对象扩展运算符
拓展运算符(...)用于取出参数对象所有可遍历属性然后拷贝到当前对象。
<script> // 1、拷贝对象 let person1 = { name: "Amy", age: 15 } let someone = { ...person1 } console.log(someone) //{name: "Amy", age: 15} // 2、合并对象 let age = { age: 15 } let name = { name: "Amy" } let person2 = { ...age, ...name } console.log(person2) //{age: 15, name: "Amy"} </script>
⑧箭头函数
箭头函数提供了一种更加简洁的函数书写方式。基本语法是:
参数 => 函数体
<script> // 传统 var f1 = function (a) { return a } console.log(f1(1)) // ES6 var f2 = a => a console.log(f2(1)) // 当箭头函数没有参数或者有多个参数,要用 () 括起来。 // 当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块, // 当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。 var f3 = (a, b) => { let result = a + b return result } console.log(f3(6, 2)) // 8 // 前面代码相当于: var f4 = (a, b) => a + b </script>
3.3、Vue复习
1、介绍
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。
Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
官方网站:https://cn.vuejs.org
初始vue.js:
<!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>Document</title> </head> <body> <script src="./vue.min.js"></script> <div id="app"> <!-- {{}} 插值表达式,绑定vue中的data数据 --> {{ message }} </div> </body> <script> // 创建vue实例 new Vue({ el: '#app', // 绑定vue作用的范围 data: { // 定义页面中显示的模型数据 message: 'Hello Vue!' } }) </script> </html>
这就是声明式渲染:Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统
这里的核心思想就是没有繁琐的DOM操作,例如jQuery中,我们需要先找到div节点,获取到DOM对象,然后进行一系列的节点操作
2、创建代码片段(快速生成代码)
文件 => 首选项 => 用户代码片段 => 新建全局代码片段/或文件夹代码片段:vue-html.code-snippets
注意:制作代码片段的时候,字符串中如果包含文件中复制过来的“Tab”键的空格,要换成“空格键”的空格
{ "vue htm": { "scope": "html", "prefix": "vuehtml", "body": [ "<!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>Document</title>", "</head>", "", "<body>", " <div id=\"app\">", "", " </div>", " <script src=\"vue.min.js\"></script>", " <script>", " new Vue({", " el: '#app',", " data: {", " $1", " }", " })", " </script>", "</body>", "", "</html>", ], "description": "my vue template in html" } }
3、基本数据渲染和指令
创建 01-基本数据渲染和指令.html
你看到的 v-bind 特性被称为指令。指令带有前缀 v-
除了使用插值表达式{{}}进行数据渲染,也可以使用 v-bind指令,它的简写的形式就是一个冒号(:)
<!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>Document</title> </head> <body> <div id="app"> <!-- v-bind指令 单向数据绑定 这个指令一般用在标签属性里面,获取值 --> <h1 title="abc"> {{content}} </h1> <h1 v-bind:title="message"> {{content}} </h1> <!-- 简写方式 --> <h1 :title="message"> {{content}} </h1> </div> <script src="vue.min.js"></script> <script> new Vue({ el: '#app', data: { content: '我是标题', message: '页面加载于 ' + new Date().toLocaleString() } }) </script> </body> </html>
4、双向数据绑定
<!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>Document</title> </head> <body> <div id="app"> <!-- v-bind:value只能进行单向的数据渲染 --> <input type="text" v-bind:value="searchMap.keyWord"> <!-- v-model 可以进行双向的数据绑定 --> <input type="text" v-model="searchMap.keyWord"> <p>您要查询的是:{{searchMap.keyWord}}</p> </div> <script src="vue.min.js"></script> <script> new Vue({ el: '#app', data: { searchMap: { keyWord: '尚硅谷' } } }) </script> </body> </html>
5、事件绑定
<!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>Document</title> </head> <body> <div id="app"> <!-- v-on 指令绑定事件,click指定绑定 的事件类型,事件发生时调用vue中 methods节点中定义的方法 --> <button v-on:click="search()">查询</button> <!-- v-on 指令的简写形式 @ --> <button @click="search()">查询</button> <p>您要查询的是:{{searchMap.keyWord}}</p> <p><a v-bind:href="result.site" target="_blank">{{result.title}}</a></p> </div> <script src="vue.min.js"></script> <script> new Vue({ el: '#app', data: { searchMap: { keyWord: '尚硅谷' }, //查询结果 result: {} }, methods: { search() { console.log('search') this.result = { "title": "尚硅谷", "site": "http://www.atguigu.com" } } }, }) </script> </body> </html>
6、修饰符
<!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>Document</title> </head> <body> <!-- Vue中的事件修饰符: 1、prevent:阻止默认事件(常用) 2、stop:阻止事件冒泡(常用) 3、once:事件只触发一次(常用) 4、capture:使用事件的捕获方式 5、self:只有event.taegent是当前操作的元素时才触发事件 6、passive:事件的默认认为立即执行,无需等待事件回调执行完毕 --> <div id="app"> <h2>欢迎来到{{name}}学习Vue!</h2> <!-- prevent:阻止默认事件(常用) --> <a href="https://www.bilibili.com" @click.prevent="showInfo">点我提示信息</a> <!-- stop:阻止事件冒泡(常用) --> <div class="demo1" @click="showInfo"> <button @click.stop="showInfo">点我一下</button> </div> <!-- once:事件只触发一次(常用) --> <button @click.once="showInfo">点我一下</button> <!-- capture:使用事件的捕获方式 --> <div class="box1" @click.capture="showMsg('box1')"> div1 <div class="box2" @click="showMsg('box2')"> div2 </div> </div> <!-- self:只有event.taegent是当前操作的元素时才触发事件 --> <div class="demo1" @click.self="showInfo"> <button @click="showInfo">点我一下</button> </div> <!-- passive:事件的默认认为立即执行,无需等待事件回调执行完毕 --> <ul @scroll="demo1" class="list"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> </ul> <ul @wheel.passive="demo2" class="list"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> </ul> </div> <script src="vue.min.js"></script> <script> new Vue({ el: '#app', data: { name: "b站" }, methods: { showInfo(e) { // e.stopPropagation();//阻止冒泡 // e.cancelBubble=true;//阻止冒泡 // e.preventDefault();//取消默认行为 console.log(e.target); alert("你好呀!"); }, showMsg(msg) { console.log("你好" + msg); }, demo1() { console.log("滚动了1"); }, demo2() { for (let i = 0; i < 100000; i++) { console.log("#"); } console.log("累坏了"); console.log("滚动了2"); } } }) </script> </body> </html>
7、条件渲染
<!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>Document</title> </head> <body> <div id="app"> <input type="checkbox" v-model="ok">同意许可协议 <!-- v:if条件指令:还有v-else、v-else-if 切换开销大 --> <h1 v-if="ok">if:Lorem ipsum dolor sit amet.</h1> <h1 v-else>no</h1> <!-- v:show 条件指令 初始渲染开销大 --> <h1 v-show="ok">show:Lorem ipsum dolor sit amet.</h1> <h1 v-show="!ok">no</h1> </div> <script src="vue.min.js"></script> <script> new Vue({ el: '#app', data: { ok: false } }) </script> </body> </html>
v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
8、列表渲染
<!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>Document</title> </head> <body> <div id="app"> <!-- 1、简单的列表渲染 --> <ul> <!-- 如果想获取索引,则使用index关键字,注意,圆括号中的index必须放在后面 --> <li v-for="(n, index) in 5">{{ n }} - {{ index }} </li> </ul> <!-- 2、遍历数据列表 --> <table border="1"> <!-- <tr v-for="item in userList"></tr> --> <tr v-for="(item, index) in userList"> <td>{{index}}</td> <td>{{item.id}}</td> <td>{{item.username}}</td> <td>{{item.age}}</td> </tr> </table> </div> <script src="vue.min.js"></script> <script> new Vue({ el: '#app', data: { userList: [ { id: 1, username: 'helen', age: 18 }, { id: 2, username: 'peter', age: 28 }, { id: 3, username: 'andy', age: 38 } ] } }) </script> </body> </html>
9、组件
组件(Component)是 Vue.js 最强大的功能之一。
组件可以扩展 HTML 元素,封装可重用的代码。
组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为
一个组件树:
10、局部组件
<!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>Document</title> </head> <body> <div id="app"> <Navbar></Navbar> </div> <script src="vue.min.js"></script> <script> new Vue({ el: '#app', //定义vue使用的组件 components: { //组件的名字 'Navbar': { //组件的模板 template: `<ul><li>首页</li><li>学员管理</li></ul>` } } }) </script> </body> </html>
11、全局组件
定义全局组件:components/Navbar.js
// 定义全局组件 Vue.component('Navbar', { template: '<ul><li>首页</li><li>学员管理</li><li>讲师管理</li></ul>' })
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>Document</title> </head> <body> <div id="app"> <Navbar></Navbar> </div> <script src="vue.min.js"></script> <script src="./components/Navbar.js"></script> <script> new Vue({ el: '#app', data: { } }) </script> </body> </html>
12、实例生命周期
<!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>Document</title> </head> <body> <div id="app"> <button @click="update">update</button> <h3 id="h3">{{ message }}</h3> </div> <script src="vue.min.js"></script> <script> new Vue({ el: '#app', data: { message: '床前明月光' }, methods: { show() { console.log('执行show方法') }, update() { this.message = '玻璃好上霜' } }, //===创建时的四个事件 beforeCreate() { // 第一个被执行的钩子方法:实例被创建出来之前执行 console.log(this.message) //undefined this.show() //TypeError: this.show is not a function // beforeCreate执行时,data 和 methods 中的 数据都还没有没初始化 }, created() { // 第二个被执行的钩子方法 console.log(this.message) //床前明月光 this.show() //执行show方法 // created执行时,data 和 methods 都已经被初始化好了! // 如果要调用 methods 中的方法,或者操作 data 中的数据,最早,只能在 created 中操作 }, beforeMount() { // 第三个被执行的钩子方法 console.log(document.getElementById('h3').innerText) //{{ message }} // beforeMount执行时,模板已经在内存中编辑完成了,尚未被渲染到页面中 }, mounted() { // 第四个被执行的钩子方法 console.log(document.getElementById('h3').innerText) //床前明月光 // 内存中的模板已经渲染到页面,用户已经可以看见内容 }, //===运行中的两个事件 beforeUpdate() { // 数据更新的前一刻 console.log('界面显示的内容:' + document.getElementById('h3').innerText) console.log('data 中的 message 数据是:' + this.message) // beforeUpdate执行时,内存中的数据已更新,但是页面尚未被渲染 }, updated() { console.log('界面显示的内容:' + document.getElementById('h3').innerText) console.log('data 中的 message 数据是:' + this.message) // updated执行时,内存中的数据已更新,并且页面已经被渲染 } }) </script> </body> </html>
13、路由
Vue.js 路由允许我们通过不同的 URL 访问不同的内容。
通过 Vue.js 可以实现多视图的单页Web应用(single page web application,SPA)。
Vue.js 路由需要载入 vue-router 库
<!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>Document</title> </head> <body> <div id="app"> <h1>Hello App!</h1> <p> <!-- 使用 router-link 组件来导航. --> <!-- 通过传入 `to` 属性指定链接. --> <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 --> <router-link to="/">首页</router-link> <router-link to="/student">会员管理</router-link> <router-link to="/teacher">讲师管理</router-link> </p> <!-- 路由出口 --> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> </div> <script src="vue.min.js"></script> <script src="vue-router.min.js"></script> <script> // 1. 定义(路由)组件。 // 可以从其他文件 import 进来 const Welcome = { template: '<div>欢迎</div>' } const Student = { template: '<div>student list</div>' } const Teacher = { template: '<div>teacher list</div>' } // 2. 定义路由 // 每个路由应该映射一个组件。 const routes = [ { path: '/', redirect: '/welcome' }, //设置默认指向的路径 { path: '/welcome', component: Welcome }, { path: '/student', component: Student }, { path: '/teacher', component: Teacher } ] // 3. 创建 router 实例,然后传 `routes` 配置 const router = new VueRouter({ routes // (缩写)相当于 routes: routes }) // 4. 创建和挂载根实例。 // 从而让整个应用都有路由功能 const app = new Vue({ el: '#app', router }) // 现在,应用已经启动了! </script> </body> </html>
本文作者:_xiaolin
本文链接:https://www.cnblogs.com/SilverStar/p/17415157.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)