application.properties文件
#springboot2.1后加cj,不会报警告
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#springboot2.1之后的版本需要加时区
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8
spring.datasource.data-username=root
spring.activemq.password=root123
实体类:
//@Data注解生成getset空参等方法
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
Mapper类:
//继承BaseMapper里的方法,使用User,增删改查
@Repository
public interface UserMapper extends BaseMapper<User> {
}
主启动类:
@SpringBootApplication
//扫描mapper接口
@MapperScan("com.yutou.mpdemo1010.mapper")
public class Mpdemo1010Application {
public static void main(String[] args) {
SpringApplication.run(Mpdemo1010Application.class, args);
}
}
测试类:
@SpringBootTest
public class Mpdemo1010ApplicationTests {
//注入usermapper
@Autowired
private UserMapper userMapper;
//查询user表所有数据
@Test
public void findAll() {
List<User> users = userMapper.selectList(null);
System.out.println(users);
}
}
测试中遇到错误:
Access denied for user ‘’@‘localhost’ to database ‘mybatis_plus’
添加以下配置:
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
//添加操作
@Test
public void addUser(){
User user = new User();
user.setName("lucy");
user.setAge(30);
user.setEmail("[email protected]");
int insert = userMapper.insert(user);
System.out.println("insert"+ insert);
}
mp自动生成id值
分布式系统唯一ID生成方案汇总
设置主键生成策略:
@TableId(type = IdType.AUTO) //给id设置自动增长
private Long id;
@Test
public void updateUser(){
User user = new User();
user.setId(2L);
user.setAge(120);
int row = userMapper.updateById(user);
System.out.println(row);
}
在表中增加create_time和update_time列
具体实现过程:
1、在实体类里面进行自动填充属性添加注解:
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
2、创建类,实现接口MetaObjectHandler实现接口里面的方法
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
//使用mp实现添加操作,这个方法执行
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
//使用mp实现修改操作,这个方法执行
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
主要解决 丢失更新
多个人同时修改一条记录,但是只能有一个人修改成功,就是最先修改的那个人
具体实现步骤:
1、表添加字段,作为乐观锁版本号
2、对应实体类添加版本号属性
@Version
private Integer version; //版本号
3、乐观锁插件
@Configuration
//扫描mapper接口
@MapperScan("com.yutou.mpdemo1010.mapper")//将主启动类的注解移动到这里
public class MpConfig {
//乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
}
4、测试乐观锁
//测试乐观锁
@Test
public void testOptimisticLocker(){
//先根据id查询数据 是为了先获取版本号
User user = userMapper.selectById(1392802232843370498L);
//进行修改
user.setAge(200);
userMapper.updateById(user);
}
mp简单查询:
1、根据id查询
2、多个id批量查询
//多个id批量查询
@Test
public void testSelectBatchIds(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1392802232843370498L,1392803855737053186L));
System.out.println(users);
}
3、简单的条件查询
1、配置分页插件
//分页插件
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
2、编写分页代码
直接new page对象,传入两个参数:当前页和每页显示记录数
//分页查询
@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());//是否有上一页
}
如果报错zero date value prohibited
在配置文件中的jdbc.url后加上
zeroDateTimeBehavior=CONVERT_TO_NULL
物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据
1、根据id删除记录
@Test
public void testDeleteById(){
int result = userMapper.deleteById(1392718513407320065L);
System.out.println(result);
}
2、批量删除:假删除,将对应数据中代表是否被删除字段状态修改为"被删除状态",之后在数据库中仍旧能看到此条数据记录
逻辑删除:
1、数据库中添加deleted字段
ALTER TABLE `user` ADD COLUMN `deleted` boolean //并在设计表中更改默认为0
2、实体类添加deleted字段
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Integer deleted;
3、配置逻辑删除插件
//逻辑删除插件
@Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}
4、配置默认0是删除,1是不删除
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
5、测试删除
使用之前的物理删除测试代码,最终效果是deleted值变成1
dev:开发环境 test:测试环境 pro:生产环境
//性能分析插件
//开发环境使用,线上不推荐,maxTime 指的是sql 最大执行时长
@Profile({"dev","test"})//设置dev test 环境开启
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100); //单位为ms,超过此处设置的ms则sql不执行
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}
环境设置:
#环境设置
spring.profiles.active=dev
测试复杂查询
//mp实现复杂查询操作
@Test
public void testSelectQuery(){
//创建QueryWrapper对象
QueryWrapper<User> wrapper = new QueryWrapper<>();
//通过QueryWrapper设置条件
//ge、gt、le、lt
//查询age>=30的记录
//第一个参数字段名称,第二个参数设置值
// wrapper.ge("age",30);
//eq、ne 搜索name=龚俊的数据
// wrapper.eq("name","龚俊");
//between
// wrapper.between("age",20,30);
//like
// wrapper.like("name","张");
//orderByDesc
// 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、表名、字段名必须使用小写字母或数字,禁止出现数字开头
3、表名不使用复数名词
4、表的命名最好加上"业务名称_表的作用"
5、表必备三字段:id;gmt_create,gmt_modified
其中id为主键,类型为bigint unsigned,单表时自增,步长为1。(如果使用分库分表集群部署,则id类型为verchar,非自增,业务中使用分布式id生成器),gmt_create,gmt_modified 的类型均为datetime类型,前者现在时表示主动创建,后者过去分词表示被动更新
6、单表行数超过500万行或者单表容量超过2GB,才推荐分库建表
7、表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是unsigned tinyint(1表示是,0表示否)
新建guli_parent项目,pom.xml中加入以下代码,统一声明包的版本号
<groupId>com.yutougroupId>
<artifactId>guli_parentartifactId>
<packaging>pompackaging>
<version>0.0.1-SNAPSHOTversion>
<name>guli_parentname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
<guli.version>0.0.1-SNAPSHOTguli.version>
<mybatis-plus.version>3.0.5mybatis-plus.version>
<velocity.version>2.0velocity.version>
<swagger.version>2.7.0swagger.version>
<aliyun.oss.verision>2.8.3aliyun.oss.verision>
<jodatime.verision>2.10.1jodatime.verision>
<poi.verision>3.17poi.verision>
<commons-fileupload.verision>1.3.1commons-fileupload.verision>
<commons-io.verision>2.6commons-io.verision>
<httpclient.verision>4.5.1httpclient.verision>
<jwt.verision>0.7.0jwt.verision>
<aliyun-java-sdk-core.verision>4.3.3aliyun-java-sdk-core.verision>
<aliyun-sdk-oss.verision>3.1.0aliyun-sdk-oss.verision>
<aliyun-java-sdk-vod.verision>2.15.2aliyun-java-sdk-vod.verision>
<aliyun-java-vod-upload.verision>1.4.11aliyun-java-vod-upload.verision>
<aliyun-sdk-vod-upload.verision>1.4.11aliyun-sdk-vod-upload.verision>
<fastjson.version>1.2.28fastjson.version>
<gson.verision>2.8.2gson.verision>
<json.verision>20170516json.verision>
<commons-dbutils.verision>1.7commons-dbutils.verision>
<canal.vlient.verision>1.1.0canal.vlient.verision>
<docker.image.prefix>zxdocker.image.prefix>
<cloud-alibaba.verision>0.2.2.RELEASEcloud-alibaba.verision>
properties>
导入依赖时,发现pom文件的引入版本号报红
是因为标签内是引入本地依赖,先导入下载完本地依赖再使用这个标签。
报红
去官网下载jar包后,复制到本地的maven中的lb文件夹下打开cmd运行,官网现在是1.4.14版
mvn install:install-file -DgroupId=com.aliyun -DartifactId=aliyun-sdk-vod-upload -Dversion=1.4.14 -Dpackaging=jar -Dfile=aliyun-java-vod-upload-1.4.14.jar
新建service模块,在其下新建service_edu模块
…/…/pom.xml
在Pom文件中此文件报红,作用是子工程继承了父工程,默认的没有配置,需要指出父工程pom文件的相对位置
配置文件内容:
#服务端口
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.activemq.password=root123
#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
使用代码生成器生成controller、mapper、service等内容:
更改代码生成器中的下面内容
gc.setOutputDir("F:\\My eclipse\\guli_parent\\service\\service_edu" + "/src/main/java");//改成绝对路径
gc.setIdType(IdType.ID_WORKER_STR); //更改主键策略
// 3、数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root123");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 4、包配置
PackageConfig pc = new PackageConfig();
//包的名字 com.yutou.eduservice
pc.setParent("com.yutou");
pc.setModuleName("eduservice"); //模块名
//com.yutou.eduservice.controller/entity/service/mapper
pc.setController("controller");
pc.setEntity("entity");
pc.setService("service");
pc.setMapper("mapper");
mpg.setPackageInfo(pc);
//里面的@Controller表示spring管理的对象,@ResponseBody还表示里面的内容需要返回数据
@RestController
//访问路径
@RequestMapping("/eduservice/edu-teacher")
public class EduTeacherController {
//把service注入
@Autowired
private EduTeacherService teacherService;
//查询讲师所有数据
//rest风格
@GetMapping("findAll")//findall前可以加"/"也可以不加
public List<EduTeacher> findAll(){
//调用service的方法实现查询所有的操作
List<EduTeacher> list = teacherService.list(null);
return list;
}
}
@SpringBootApplication
public class EduApplication {
public static void main(String[] args) {
SpringApplication.run(EduApplication.class,args);
}
}
@Configuration
@MapperScan("com.yutou.eduservice.mapper")
public class MybatisPlusConfig {
}
报错Failed to configure a DataSource: ‘url’ attribute is not specified and no embedd
把pom文件中的pom去掉,可以成功运行,但是…/…/pom.xml报红
更改返回的Json时间格式
#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
在config中配置逻辑删除插件:
//逻辑删除插件
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
逻辑删除上添加注解
@TableLogic
private Boolean isDeleted;
逻辑删除方法:
//逻辑删除讲师方法
@DeleteMapping("{id}")//id值需要通过路径进行传递
//@PathVariable 获取路径中的id值
public boolean removeTeacher(@PathVariable String id){
boolean flag = teacherService.removeById(id);
return flag;
}
如何测试:因为使用delete提交
借助一些工具进行测试:swagger或Postman
前后端分离开发模式中,api文档是最好的沟通方式。
Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。
创建公共模块,整合swagger,为了所有模块都能进行使用:
1、guli_parent创建子模块common,在common创建子模块 service_base
配置类如下:
@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("Helen", "http://atguigu.com",
"[email protected]"))
.build();
}
}
2、在service_edu引入service_base依赖
<dependency>
<groupId>com.yutougroupId>
<artifactId>service_baseartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
3、在service_edu启动类添加注解,设置包扫描规则
//为了加载swagger的配置类
@ComponentScan(basePackages = {"com.yutou"})
public class EduApplication {..}
4、访问swaggerSwagger UI
报错unable to infer base url. This is common when using dynamic servlet registration or when the API is behind an API Gateway
把@ComponentScan(basePackages = {“com.yutou”})注释掉换成@EnableSwagger2
5、定义接口说明和参数说明
定义在类上:@Api
定义在方法上:@ApiOperation
定义在参数上:@ApiParam
统一返回数据格式:
项目中我们会将响应封装成json返回,一般我们会将所有的接口的数据格式统一,使得前端对数据的操作更一致轻松
一般情况下,统一返回数据格式没有固定的格式,只要能描述清楚返回的数据状态以及要返回的具体数据就可以。但是一般会包含状态码、返回消息、数据这几部分内容
json数据格式2种:
对象、数组两种格式混合使用
{
"success":布尔 //响应是否成功
"code":数字 //响应码
"message": 字符串 //返回消息
"data": HashMap //返回数据,放在键值对中
}
1、在common模块中创建子模块common-utils
2、创建interface,定义数据返回状态码
public interface ResultCode {
public static Integer SUCCESS = 20000;//成功
public static Integer ERROR = 20001;//失败
}
3、创建结果类
链式编程
形如以下这种的叫做链式编程,每个类都返回this即可实现下面的
4、 在service中引入common-utils
<dependency>
<groupId>com.yutougroupId>
<artifactId>common-utilsartifactId>
<version>0.0.1-SNAPSHOTversion>
dependency>
public R findAll(){
//调用service的方法实现查询所有的操作
List<EduTeacher> list = teacherService.list(null);
return R.ok().data("items",list);
}
public R removeTeacher(@ApiParam(name = "id", value = "讲师ID", required = true)
@PathVariable String id){
boolean flag = teacherService.removeById(id);
if (flag){
return R.ok();
}else {
return R.error();
}
}
测试
1、MybatisPlusConfig中配置分页插件
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
2、编写讲师分页查询接口的方法
//分页查询讲师方法
//current 当前页
//limit 每页记录数
@GetMapping("pageTeacher/{current}/{limit}")
public R pageListTeacher(@PathVariable long current,
@PathVariable long limit){
//创建page对象
Page<EduTeacher> pageTeacher = new Page<>(current,limit);
//调用方法实现分页
//调用方法的时候,底层封装,把分页所有数据封装到pageTeacher对象里面
teacherService.page(pageTeacher,null);
long total = pageTeacher.getTotal(); //总记录数
List<EduTeacher> records = pageTeacher.getRecords(); //数据list集合
//等价于下面那种
// Map map = new HashMap();
// map.put("total",total);
// map.put("row",records);
// return R.ok().data(map);
return R.ok().data("total",total).data("rows",records);
}
1、把条件值传递到接口里面
把条件值封装到对象里面,把对象传递到接口里面
@ApiModel(value = "Teacher查询对象" ,description = "讲师查询对象封装")
@Data
public class TeacherQuery implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "教师名称,模糊查询")
private String name;
@ApiModelProperty(value = "头衔 1高级讲师 2首席讲师")
private Integer level;
@ApiModelProperty(value = "查询开始时间", example = "2019-01-01 10:10:10")
private String begin;//注意,这里使用的是String类型,前端传过来的数据无需进行类型转换
@ApiModelProperty(value = "查询结束时间", example = "2019-12-01 10:10:10")
private String end;
}
2、根据条件值进行判断,拼接条件
@RequestBody:使用json传递数据,把json数据封装到对应的对象里面 (需要使用Post提交)
@ResponsBody:返回数据,返回Json数据
//条件查询带分页的方法
@PostMapping("pageTeacherCondition/{current}/{limit}")
public R pageTeacherCondition(@PathVariable long current,
@PathVariable long limit,
//参数值可以为空
@RequestBody(required = false) TeacherQuery teacherQuery){
//创建page对象
Page<EduTeacher> teacherPage = new Page<>(current,limit);
//构建条件
QueryWrapper<EduTeacher> wrapper = new QueryWrapper<>();
//多条件组合拆线呢
//mybatis 学过 动态sql
String name = teacherQuery.getName();
Integer level = teacherQuery.getLevel();
String begin = teacherQuery.getBegin();
String end = teacherQuery.getEnd();
//判断条件值是否为空,如果不为空拼接条件
if(StringUtils.isEmpty(name)){
//构建条件
wrapper.like("name",name);
}
if (StringUtils.isEmpty(level)){
wrapper.eq("level",level);
}
//begin大于 end小于 是一个范围
if (StringUtils.isEmpty(begin)){
wrapper.ge("gmt_create",begin);
}
if (StringUtils.isEmpty(end)){
wrapper.le("gmt_create",end);
}
//调用方法实现条件查询分页
teacherService.page(teacherPage,wrapper);
long total = teacherPage.getTotal(); //总记录数
List<EduTeacher> records = teacherPage.getRecords(); //数据list集合
return R.ok().data("total",total).data("rows",records);
}
1、创建包handler,创建自动填充类 MyMetaObjectHandler
@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);
}
}
2、在实体类添加自动填充注解
@ApiModelProperty(value = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
@ApiModelProperty(value = "更新时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
3、controller类
@PostMapping("addTeacher")
public R addTeacher(@RequestBody EduTeacher eduTeacher){
boolean save = teacherService.save(eduTeacher);
if (save){
return R.ok();
}else {
return R.error();
}
}
4、swagger测试
把数据中时间和id删去,,会自动填充
1、根据讲师id进行查询
//根据讲师id进行查询
@GetMapping("getTeacher/{id}")
public R getTeacher(@PathVariable String id){
EduTeacher eduTeacher = teacherService.getById(id);
return R.ok().data("teacher",eduTeacher);
}
2、讲师修改
//讲师修改功能
@PostMapping("updateTeacher")
public R updateTeacher(@RequestBody EduTeacher eduTeacher){
boolean flag = teacherService.updateById(eduTeacher);
if (flag){
return R.ok();
}else {
return R.error();
}
}
@ControllerAdvice
public class GlobalExceptionHandler {
//指定出现什么异常都会执行这个方法
@ExceptionHandler(Exception.class)
@ResponseBody //为了能够返回数据
public R error(Exception e){
e.printStackTrace();
return R.error().message("执行了全局异常处理");
}
}
//指定异常处理
@ExceptionHandler(ArithmeticException.class)
@ResponseBody //为了能够返回数据
public R error(ArithmeticException a){
a.printStackTrace();
return R.error().message("执行了ArithmeticException异常处理");
}
1、创建自定义异常类
@Data
@AllArgsConstructor //生成有参数的构造方法
@NoArgsConstructor //生成无参构造方法
public class GuliException extends RuntimeException{
private Integer code;// 状态码
private String meg; //异常信息
}
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=10/0;
}catch (Exception e){
//执行自定义异常
throw new GuliException(20001,"执行了自定义异常处理");
}
日志记录器的行为是分等级的。
分为:OFF、FATAL、ERROR、WARN、INFO、DUBUG、ALL
越往右级别越高,右边包含左边
默认情况下,spring boot 从控制台打印出来的日志级别只有INFO及以上的级别,可以配置日志级别
#设置日志级别
logging.level.root=WARN
把日志不仅输出到控制台,也可以输出到文件中,使用日志工具
1、删除application.properties日志配置
2、resource中创建logback-spring.xml
复制代码好像有问题。。略过
安装
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现
let的作用范围
<script>
//es6如何定义变量,定义变量特点
//js定义:var a = "1";
//es6写法定义变量: 使用关键字 let let a = 10;
//创建代码块,定义变量
{
var a = 10
let b = 20
}
//在代码块外面输出变量
console.log(a)
console.log(b)//Uncaught ReferenceError : b is not defined
</script>
let只能定义一次
const定义常量,值不能改变,一旦声明必须初始化,否则会报错
解构赋值
//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)
模板字符串
模板字符串相当于加强版的字符串,用反引号 `,除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量和表达式。
// 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!
声明对象简写:
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"}
定义方法简写:
// 传统
const person1 = {
sayHi:function(){
console.log("Hi")
}
}
person1.sayHi();//"Hi"
// ES6
const person2 = {
sayHi(){
console.log("Hi")
}
}
person2.sayHi() //"Hi"
对象扩展运算符:
// 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"}
箭头函数:
// 传统
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
Vue 是一套用于构建用户界面的渐进式框架。
Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
1、创建Html页面,使用vscode快捷键生成html代码 !
2、引入vue的js文件,类似于jquery
<script src="vue.min.js">script>
3、在html页面创建div标签,div添加id属性
4、编写vue代码
<script>
new Vue({
el:'#app',//绑定vue作用的范围
data:{//定义页面中显示的模型数据
message:'Hello Vue'
}
})
script>
5、使用插值表达式,获取data里面定义值
<div id="app">
{{message}}
</div>
1、基本数据渲染和指令
单向数据绑定
<div id="app">
<h1 v-bind:title="message">
{{content}}
h1>
<h2 :title="message">
{{content}}
h2>
div>
<script src="vue.min.js">script>
<script>
new Vue({
el: '#app',
data: {
content:'我是标题',
message:'页面加载于' + new Date().toLocaleString()
}
})
script>
双向数据绑定:
<body>
<div id="app">
<input type="text" v-bind:value="searchMap.keyWord"/>
<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>
vue绑定事件
<body>
<div id="app">
<button v-on:click="search()">查询button>
<button @click="f1()">查询1button>
div>
<script src="vue.min.js">script>
<script>
new Vue({
el: '#app',
data: {
searchMap:{
keyWord: '鱼头'
},
//查询结果
result:{}
},
methods:{//定义多个方法
search(){
console.log('search...')
},
f1(){
console.log('f1...')
}
}
})
script>
body>
修饰符
<div id="app">
<form action="save" v-on:sumbit.prevent="onSubmit">
<input type="text" id="name" v-model="user.username"/>
<button type="submit">保存button>
form>
div>
<script src="vue.min.js">script>
<script>
new Vue({
el: '#app',
data: {
user:{}
},
methods:{
onSubmit(){
if(this.user.username){
console.log('提交表单')
}else {
alert('请输入用户名')
}
}
}
})
script>
条件渲染
<body>
<div id="app">
<input type="checkbox" v-model="ok"/>是否同意
<h1 v-if="ok"尚硅谷>h1>
<h1 v-else>谷粒学院h1>
div>
<script src="vue.min.js">script>
<script>
new Vue({
el: '#app',
data: {
ok:false
}
})
script>
body>
循环指令
<body>
<div id="app">
<ul>
<li v-for="n in 10">{{ n }} li>
ul>
<ol>
<li v-for="(n,index) in 10">{{n}} -- {{index}}li>
ol>
<hr/>
<table border="1">
<tr v-for="user in userList">
<td>{{user.id}}td>
<td>{{user.username}}td>
<td>{{user.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>
局部组件
<body>
<div id="app">
<Navbar>Navbar>
div>
<script src="vue.min.js">script>
<script>
new Vue({
el: '#app',
// 定义局部组件,这里可以定义多个局部组件
components: {
//组件的名字
'Navbar': {
//组件的内容
template: '- 首页
- 学员管理
'
}
}
})
script>
body>
全局组件:
<script src="Navbar.js">script>
vue生命周期:
<script>
new Vue({
el: '#app',
data: {
},
created(){
debugger//断点
//在页面渲染之前执行
console.log('created..')
},
mounted(){
debugger
//在页面渲染之后执行
console.log('mounted..')
}
})
script>
vue路由
Vue.js 路由允许我们通过不同的 URL 访问不同的内容。
通过 Vue.js 可以实现多视图的单页Web应用(single page web application,SPA)。
Vue.js 路由需要载入 vue-router 库
<div id="app">
<h1>Hello App!h1>
<p>
<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: '欢迎' }
const Student = { template: 'student list' }
const Teacher = { template: 'teacher list' }
// 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>
axios是独立的项目,不是vue里面的一部分,使用axios经常和vue一起使用,实现ajax操作
1、创建html页面,引入js文件,包含两个js文件,vue和axios
<script src="vue.min.js">script>
<script src="axios.min.js">script>
2、编写axios代码
创建json文件,数据创建
{
"success":true,
"code":20000,
"message":"成功",
"data":{
"items":[
{"name":"lucy","age":"20"},
{"name":"zzh","age":"30"},
{"name":"gj","age":"29"}
]
}
}
使用axios发送ajax请求,请求文件,得到数据,在页面显示
<body>
<div id="app">
</div>
<script src="vue.min.js"></script>
<script src="axios.min.js"></script>
<script>
new Vue({
el: '#app',
//固定的结构
data: { //在data定义变量和初始值
//定义变量:值空数组
userList:[]
},
created() {//在页面渲染之前执行
//调用定义的方法
this.getUserList()
},
methods:{//编写具体的方法
//创建方法 查询所有用户数据
getUserList() {
//使用axios发送ajax请求
//axios:提交方式().then(箭头函数).catch(箭头函数)
axios.get("data.json")
.then(response => {
//response就是请求之后返回数据
// console.log('***'+response)
//通过response获取具体数据,赋值给定义空数组
this.userList = response.data.data.items
console.log(this.userList)
}) //请求成功执行then方法
.catch(error =>{
}) //请求失败执行catch方法
}
}
})
</script>
</body>
简单的说 Node.js 就是运行在服务端的 JavaScript。
Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。
编写js文件
const http = require('http');
http.createServer(function (request, response){
//发送HTTP头部
//HTTP 状态值: 200 : OK
//内容类型:text/plain
response.writeHead(200,{'Content-Type':'text/plain'});
//发送响应数据 "Hello World"
response.end("Hello Server");
}).listen(8888);
//终端打印如下信息
console.log('Server running at http://127.0.0.1:8888/');
在js文件所在文件夹运行cmd
输入node 01.js即可以运行。 在浏览器输入http://127.0.0.1:8888/可以访问到hello Sever
类似maven,用在前端中,管理前端js依赖,联网下载js依赖,比如jquery
1、npm项目初始化操作
使用命令
npm init -y
2、npm下载js依赖
babel是转码器,把es6代码换成es5代码,因为写的代码es6代码,但是es6代码浏览器兼容性很差,如果使用es5代码浏览器兼容性好
1、安装babel工具,使用命令
npm install --save-dev babel-cli
如果babel --version显示不了版本号
如果安装后已经产生了node-module文件夹,就把里面的.bin文件加入到环境变量Path中,重启vscode,即可看到版本号
2、创建js文件,编写cs6代码
3、创建babel配置文件
{
"presets":["es2015"],
"plugins":[]
}
4、安装转码器
npm install --save-dev babel-preset-es2015
5、使用命令进行转码
babel es6/demo.js -o dist/01.js
如果package.json里面的babel版本号是6.x的,而babel --version的版本是7.x则出现错误
原因是版本不兼容,把之前安装的卸载之后
按以下地址的教程安装:
babel7.0 安装及使用_Yu_xiaoji的博客-CSDN博客_安装babel7
但是安装之后转换不了。。。
开发后端接口时候,开发controller service mapper,controller注入service,service注入mapper,在后端中,类与类之间的调用称为后端模块化操作
前端模块化
新建01.js
//创建js方法
// 定义成员:
const sum = function(a,b){
return parseInt(a) + parseInt(b) }
const subtract = function(a,b){
return parseInt(a) - parseInt(b) }
//设置哪些方法可以被其他js调用
module.exports = {
sum: sum,
subtract: subtract,
}
新建02.js
//1 引入01.js文件
const m = require('./01.js')
//2.调用
console.log(m.sum(1,2))
console.log(m.subtract(3,1))
新建01.js
//定义方法,设置哪些方法可以被其他js调用
export function getList(){
console.log('getList...')
}
export function save(){
console.log('save...')
}
新建02.js
//调用01.js的方法,引入01.js文件,进行调用
import {getList,save} from './01.js'
//调用方法
getList()
save()
webpack是一个前端资源加载/打包工具,它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的的规则生成对应的静态资源
把多种静态资源转换成一个静态文件,减少了页面的请求
安装webpack,把webpack的.bin路径加入环境变量
使用webpack -v查看是否安装成功
1、创建src/common.js文件
exports.info = function (str) {
document.write(str);
}
2、创建src/utils.js文件
exports.add = function (a, b) {
return a + b; }
3、src下创建main.js
const common = require('./common');
const utils = require('./utils');
common.info('Hello world!' + utils.add(100, 200));
4、创建配置文件webpack.config,js
const path = require("path"); //Node.js内置模块
module.exports = {
entry: './src/main.js', //配置入口文件
output: {
path: path.resolve(__dirname, './dist'), //输出路径,__dirname:当前文件所在路
径
filename: 'bundle.js' //输出文件
}
}
5、命令行执行编译命令
webpack #有黄色警告
webpack --mode=development #没有警告
#执行后查看bundle.js 里面包含了上面两个js文件的内容并惊醒了代码压缩
6、测试
webpack目录下创建index.html,引用bundle.js
<body>
<script src="dist/bundle.js">script>
body>
1、创建src/css文件
2、在main.js中引入css文件
//css文件引入
require('./style.css')
3、安装css-loader和style-loader
webpack本身只能处理javaScript模块,如果要处理其他类型的文件,就需要使用loader进行转换。
npm install --save-dev style-loader css-loader
4、修改webpackconfig.js
const path = require("path"); //Node.js内置模块
module.exports = {
entry: './src/main.js', //配置入口文件
output: {
path: path.resolve(__dirname, './dist'), //输出路径,__dirname:当前文件所在路径
filename: 'bundle.js' //输出文件
},//新增部分
module: {
rules: [
{
test: /\.css$/, //打包规则应用到以css结尾的文件上
use: ['style-loader', 'css-loader']
}
]
}
}
# 解压压缩包
# 进入目录
cd vue-element-admin-master
# 安装依赖
npm install
# 启动。执行后,浏览器自动弹出并访问http://localhost:9527/
npm run dev
前端框架入口
index.html 和 main.js
build:放项目构建的脚本文件
config:配置信息
可以更改端口号
不安装Eslint插件,因为语法要求过于严格
src目录
1、更改dev.env.js里面的访问地址
2、进行登录调用两个方法, Login登录操作方法和info登录之后获取用户信息的方法,所以,创建接口两个方法实现登录
(1)login 返回 token值
(2)info 返回 roles name avatar
@RestController
@RequestMapping("/eduservice/user")
public class EduLoginController {
//login
@PostMapping("login")
public R login(){
return R.ok().data("token","admin");
}
//info
@GetMapping("info")
public R info(){
return R.ok().data("roles","[admin]").data("name","admin").data("avatar","https://img1.doubanio.com/view/richtext/large/public/p155631507.jpg");
}
}
跨域问题:
通过一个地址去访问另外一个地址,这个过程中如果有三个地方任何一个不一样:
在类上增加注解:
@CrossOrigin //解决跨域问题
1、添加路由
2、创建路由对应的页面
新建以下路径下的vue文件
3、创建api/teacher.js定义访问路由的接口地址
import request from '@/utils/request'
export default {
//1 讲师(条件查询分页)
//current当前页 limit每页记录数 teacherQuery
getTeacherListPage(current, limit, teacherQuery){
return request({
url: `/eduservice/teacher/pageteacherCondition/${current}/${limit}`,
method: 'post',
//teachQuery条件对象,后端使用ReqeustBody获取数据
//data表示把对象转换json进行传递到接口里面
data: teacherQuery
})
}
}
4、在讲师列表页面 list.vue页面调用定义的接口方法,得到接口返回数据
讲师列表
5、把请求接口获取参数在页面进行显示
讲师列表
{{ (page - 1) * limit + scope.$index + 1 }}
{{ scope.row.level===1?'高级讲师':'首席讲师' }}
修改
删除
增加分页条
分页的方法修改,添加页码参数
测试:
查 询
清空
实现清空功能:
resetData(){//清空的方法
//表单输入项数据情况
this.teacherQuery = {}
//查询所有讲师数据
this.getList()
}
1、在每条记录后面添加删除按钮
2、在按钮绑定事件
3、在绑定事件的方法传递删除讲师的id值
//删除讲师方法
removeDataById(id){
alert(id)
}
4、在api文件夹teacher.js定义删除接口的地址
//删除讲师
deleteTeacherId(id){
return request({
url: `/eduservice/teacher/${id}`,
method: 'delete'
})
}
5、页面调用方法实现删除
//删除讲师方法
removeDataById(id){
this.$confirm('此操作将永久删除讲师记录, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => { //点击确定,执行then方法
//调用删除方法
teacher.deleteTeacherId(id)
.then(response =>{
//提示信息
this.$message({
type: 'success',
message: '删除成功!'
});
//回到列表页面
this.getList()
})
}) //点击取消,执行catch方法
}
给save页面添加模板
讲师添加
保存
在teacher.js中
//添加讲师
addTeacher(teacher){
return request({
url: `/eduservice/teacher/addTeacher`,
method: 'post',
data: teacher
})
}
2、在页面实现调用
//添加讲师的方法
saveTeacher(){
teacherApi.addTeacher(this.teacher)
.then(response =>{
//提示信息
this.$message({
type: 'success',
message:'添加成功'
});
//回到列表页面 路由跳转
this.$router.push({path:'/teacher/table'})
})
}
中文出现乱码,在数据库的连接后面加上
spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8
3、增加排序功能
//排序
queryWrapper.orderByDesc("gmt_create");
4、测试
排序没用
1、在每条记录后面添加 修改 按钮
2、点击修改按钮,进入表单页面,进行数据回显
3、通过路由跳转进入数据回显页面,在路由index页面添加路由
{
path:'edit/:id',
name:'EduTeacherEdit',
component:() => import('@/views/edu/teacher/save'),
meta:{title:'编辑讲师',noCache:'true'},
hidden:true
}
更改
4、在表单页面实现数据回显
(1)在teacher.js定义根据id查询接口
getTeacherInfo(id){
return request({
url: `/eduservice/teacher/getTeacher/${id}`,
method: 'get'
})
}
(2)在页面调用接口实现数据回显
//根据讲师id查询的方法
getInfo(id){
teacherApi.getTeacherInfo(id)
.then(response => {
this.teacher = response.data.teacher
})
},
(3)调用 根据id查询的方法
因为添加和修改使用save页面。区别添加和修改,只有修改时候查询数据回显
判断路径里面是否有讲师id值,如果有id值修改,没有id值直接添加
created(){//页面渲染之前执行
//判断路径是否有id值
if(this.$route.params && this.$route.params.id){
//从路径获取id值
const id = this.$route.params.id
//调用根据id查询的方式
this.getInfo(id)
}
},
最终修改页面:
1、在api的teacher.js定义修改接口
//修改讲师
updateTeacher(){
return request({
url: `/eduservice/teacher/updateTeacher`,
method: 'post',
data:teacher
})
}
2、在页面调用修改方法
saveOrUpdate(){
//判断修改还是添加
//根据teacher里面是否有id
if(this.teacher.id){
//添加
this.saveTeacher()
}else{
//修改
this.updateTeacher()
}
//添加
this.saveTeacher()
},
增加修改讲师的方法
//修改讲师的方法
updateTeacher(){
teacherApi.updateTeacherInfo(this.teacher)
.then(response =>{
//提示信息
this.$message({
type: 'success',
message:'修改成功'
});
//回到列表页面 路由跳转
this.$router.push({path:'/teacher/table'})
})
},
遇到问题:
1、第一次点击修改,进行数据回显
第二次再去点击,添加讲师,进入表单页面,但是问题:表单页面还是显示修改回显数据,正确效果应该是表单数据清空
解决问题:
上面的代码没有解决问题,为什么?
多次路由跳转到同一个页面,在页面中created方法只会执行第一次,后面再进行调整就不会执行
最终解决,使用vue监听
watch:{ //监听
$route(to, from){ //路由变化方式,当路由发送变化,方法就会执行
this.init()
}
},
为了解决海量数据存储与弹性扩容,项目中我们采用云存储的解决方案-阿里云OSS
1、使用oss,首先创建Bucket
2、准备工作,创建操作阿里云oss许可证(阿里云颁发id和密钥)
3、查看文档位置,引入依赖安装
<dependency>
<groupId>com.aliyun.ossgroupId>
<artifactId>aliyun-sdk-ossartifactId>
<version>3.10.2version>
dependency>
1、新建service.service_oss模块
2、引入依赖
<dependencies>
<dependency>
<groupId>com.aliyun.ossgroupId>
<artifactId>aliyun-sdk-ossartifactId>
dependency>
<dependency>
<groupId>joda-timegroupId>
<artifactId>joda-timeartifactId>
dependency>
dependencies>
3、配置文件
#服务端口
server.port=8002
#服务名
spring.application.name=service-oss
#环境设置:dev、test、prod
spring.profiles.active=dev
#阿里云 OSS
#不同的服务器,地址不同
aliyun.oss.file.endpoint=your endpoint
aliyun.oss.file.keyid=your accessKeyId
aliyun.oss.file.keysecret=your accessKeySecret
#bucket可以在控制台创建,也可以使用java代码创建
aliyun.oss.file.bucketname=guli-file
4、启动主配置类
加上该注解
1、创建常量类,读取配置文件的内容
//当项目已启动,spring接口,spring卸载之后,执行接口一个方法
@Component
public class ConstantPropertiedUtils implements InitializingBean {
//读取配置文件内容
@Value("${aliyun.oss.file.endpoint}")
private String endpoint;
@Value("aliyun.oss.file.keyid")
private String keyId;
@Value("aliyun.oss.file.keysecret")
private String keySecret;
@Value("aliyun.oss.file.bucketname")
private String bucketName;
//定义公开静态常量
public static String END_POINT;
public static String ACCESS_KEY_ID;
public static String ACCESS_KEY_SECRET;
public static String BUCKET_NAME;
@Override
public void afterPropertiesSet() throws Exception {
END_POINT = endpoint;
ACCESS_KEY_ID = keyId;
ACCESS_KEY_SECRET = keySecret;
BUCKET_NAME = bucketName;
}
}
2、创建controller,创建service
@RestController
@RequestMapping("/eduoss/fileoss")
public class OssController {
@Autowired
private OssService ossService;
//上传头像的方法
@PostMapping
public R uploadOssFile(MultipartFile file){
//获取上传文件 MultipartFile
//返回上传到oss的路径
String url = ossService.uploadFileAvatar(file);
return R.ok().data("url", url);
}
}
3、在service实现上传文件到oss
@Service
public class OssServiceImpl implements OssService {
//上传文件到oss
@Override
public String uploadFileAvatar(MultipartFile file) {
//工具类获取值
String endpoint = ConstantPropertiedUtils.END_POINT;
String accessKeyId = ConstantPropertiedUtils.ACCESS_KEY_ID;
String accessKeySecret = ConstantPropertiedUtils.ACCESS_KEY_SECRET;
String bucketName = ConstantPropertiedUtils.BUCKET_NAME;
try{
// 创建OSSClient实例
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
//获取上传文件的输入流
InputStream inputStream = file.getInputStream();
//获取文件名称
String fileName = file.getOriginalFilename();
// 调用oss方法实现上传
//第一个参数 Bucket名称 第二个参数 上传到oss文件路径和文件名称
//第三个参数 上传文件输入流
ossClient.putObject(bucketName, fileName, inputStream);
// 关闭OSSClient。
ossClient.shutdown();
//把上传之后的文件路径返回
//需要把上传到阿里oss路径手动拼接出来
String url = "http://"+bucketName+"."+endpoint+"/"+fileName;
return url;
}catch (Exception e){
e.printStackTrace();
return null;
}
}
}
4、测试 上传
在swagger中上传测试,注意文件不能太大
在文件名称添加随机唯一值,让每个文件名称不同 把文件进行分类管理
实现年月日分类
// 在文件名称里面添加随机唯一的值
String uuid = UUID.randomUUID().toString().replaceAll("-","");
//
fileName = uuid + fileName;
//把文件按照日期进行分类
//获取当前日期
String datePath = new DateTime().toString("yyyy/MM/dd");
//拼接
fileName = datePath+ "/"+ fileName;
下载nginx windows版
配置nginx:
在nginx.conf里面修改默认端口
配置nginx转发规则
1、将组件复制到项目src/components里面
2、添加讲师页面使用这个复制上传组件
更换头像
3、使用组件
//上传弹框组件是否显示
imagecropperShow:false,
imagecropperKey:0,//上传组件的key值
BASE_API:process.env.BASE_API,//获取dev.env.js里面地址
saveBtnDisabled:false //保存按钮是否禁用
4、引入组件和声明组件
5、修改上传接口地址
EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数量一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析
1、引入依赖
需要poi依赖配套,在guli-parent里已经引入
<dependency>
<groupId>com.alibabagroupId>
<artifactId>easyexcelartifactId>
<version>2.1.1version>
dependency>
2、创建实体类,与excel中的数据对应
@Data
public class DemoData {
//设置excel表头名称
@ExcelProperty("学生编号")
private Integer sno;
@ExcelProperty("学生姓名")
private String sname;
}
3、测试类
public class TestEasyExcel {
public static void main(String[] args) {
//实现excel写的操作
//设置写入文件夹地址和excel文件名称
String filename = "G:\\write.xlsx";
//调用easyexcel里面的操作实现写操作
//write方法里两个参数,第一个参数是文件路径名称,第二个参数是实体类的名称
EasyExcel.write(filename,DemoData.class).sheet("学生列表").doWrite(getData());
}
//创建一个方法返回list集合
private static List<DemoData> getData(){
List<DemoData> list = new ArrayList<>();
for (int i = 0; i < 10 ;i++){
DemoData data = new DemoData();
data.setSno(i);
data.setSname("simon"+i);
list.add(data);
}
return list;
}
}
1、创建和excel对应实体类,标记对应列关系
@Data
public class DemoData {
//设置excel表头名称
@ExcelProperty(value = "学生编号",index = 0)
private Integer sno;
@ExcelProperty(value = "学生姓名", index = 1)
private String sname;
}
2、创建监听进行excel文件读取
public class ExcelListener extends AnalysisEventListener<DemoData> {
//一行一行的读取excel内容
@Override
public void invoke(DemoData demoData, AnalysisContext analysisContext) {
System.out.println("***"+demoData);
}
//读取表头内容
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表头"+headMap);
}
//读取完成之后
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
3、测试类
public static void main(String[] args) {
//实现excel读的操作
//设置读入文件夹地址和excel文件名称
String filename = "G:\\write.xlsx";
EasyExcel.read(filename,DemoData.class,new ExcelListener()).sheet().doRead();
}
1、引入easyexcel依赖
2、使用代码生成器把课程分类代码生成
把代码生成器里面的更改一下,生成
3、创建实体类和excel对应关系
@Data
public class SubjectData {
@ExcelProperty(index = 0)
private String oneSubjectName;
@ExcelProperty(index = 1)
private String twoSubjectName;
}
4、编写具体类
controller类:
@RestController
@RequestMapping("/eduservice/subject")
@CrossOrigin
public class EduSubjectController {
@Autowired
private EduSubjectService subjectService;
//添加课程分类
//获取上传过来的文件,把文件内容读取出来
@PostMapping("addSubject")
public R addSubject(MultipartFile file){
//上传过来excel文件
subjectService.saveSubject(file, subjectService);
return R.ok();
}
}
service类:
public interface EduSubjectService extends IService<EduSubject> {
//添加课程分类
void saveSubject(MultipartFile file, EduSubjectService subjectService);
}
serviceImpl类:
@Service
public class EduSubjectServiceImpl extends ServiceImpl<EduSubjectMapper, EduSubject> implements EduSubjectService {
//添加课程分类
@Override
public void saveSubject(MultipartFile file, EduSubjectService subjectService) {
try{
//文件输入流
InputStream in = file.getInputStream();
//调用方法进行读取
EasyExcel.read(in, SubjectData.class,new SubjectExcelListener(subjectService)).sheet().doRead();
}catch (Exception e){
e.printStackTrace();
}
}
}
监听:
public class SubjectExcelListener extends AnalysisEventListener<SubjectData> {
//因为SubjectExcelListener不能交给spring进行管理,需要自己new,不能注入其他对象
//不能实现数据库操作
public EduSubjectService subjectService;
public SubjectExcelListener(){}
public SubjectExcelListener(EduSubjectService subjectService){
this.subjectService = subjectService;
}
//判断一级分类不能重复添加
private EduSubject existOneSubject(EduSubjectService subjectService,String name){
QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
wrapper.eq("title",name);
wrapper.eq("parent_id","0");
EduSubject oneSubject = subjectService.getOne(wrapper);
return oneSubject;
}
//判断二级分类不能重复添加
private EduSubject existTwoSubject(EduSubjectService subjectService,String name,String pid){
QueryWrapper<EduSubject> wrapper = new QueryWrapper<>();
wrapper.eq("title",name);
wrapper.eq("parent_id",pid);
EduSubject twoSubject = subjectService.getOne(wrapper);
return twoSubject;
}
//读取excel内容,一行一行进行读取
@Override
public void invoke(SubjectData subjectData, AnalysisContext analysisContext) {
if (subjectData == null){
throw new GuliException(20001,"文件数据为空");
}
//一行一行,每次读取有两个值,第一个值一级分类,第二个值二级分类
//判断一级分类是否重复
EduSubject existOneSubject = this.existOneSubject(subjectService, subjectData.getOneSubjectName());
if (existOneSubject == null){ //没有相同一级分类,进行添加
existOneSubject = new EduSubject();
existOneSubject.setParentId("0");
existOneSubject.setTitle(subjectData.getOneSubjectName());//一级分类名称
subjectService.save(existOneSubject);
}
//获取一级分类的id值
String pid = existOneSubject.getId();
//添加二级分类
//判断二级分类是否重复
EduSubject existTwoSubject = this.existTwoSubject(subjectService, subjectData.getTwoSubjectName(), pid);
if (existTwoSubject == null){
existTwoSubject = new EduSubject();
existTwoSubject.setParentId(pid);
existTwoSubject.setTitle(subjectData.getTwoSubjectName());//二级分类名称
subjectService.save(existTwoSubject);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}
实体类:
@Data
public class SubjectData {
@ExcelProperty(index = 0)
private String oneSubjectName;
@ExcelProperty(index = 1)
private String twoSubjectName;
}
1、添加课程分类路由
{
path: '/subject',
component: Layout,
redirect: '/subject/list',
name: '课程分类管理',
meta: { title: '课程分类管理', icon: 'example' },
children: [
{
path: 'list',
name: '课程分类列表',
component: () => import('@/views/edu/teacher/list'),
meta: { title: '课程分类列表', icon: 'table' }
},
{
path: 'save',
name: '添加课程分类',
component: () => import('@/views/edu/teacher/save'),
meta: { title: '添加课程分类', icon: 'tree' }
}
]
},
2、创建课程分类页面,修改路由对应的页面路径
3、在添加课程分类页面,实现效果:
创建接口,把分类按照要求的格式返回数据即可。
1、针对返回数据创建对应的实体类,两个实体类:一级和二级分类
//一级分类
@Data
public class OneSubject {
private String id;
private String title;
}
2、在两个实体类之间表示关系(一个一级分类有多个二级分类)
//一个一级分类有多个二级分类
private List<TwoSubject> children = new ArrayList<>();
3、编写具体封装代码
//课程分类列表(树形)
@Override
public List<OneSubject> getAllOneSubject() {
//1 查询所有一级分类 parentId = 0
QueryWrapper<EduSubject> wrapperOne = new QueryWrapper<>();
wrapperOne.eq("parent_id","0");
List<EduSubject> oneSubjectList = baseMapper.selectList(wrapperOne);
//2 查询所有二级分类 parentID != 0
QueryWrapper<EduSubject> wrapperTwo = new QueryWrapper<>();
wrapperTwo.ne("parent_id","0");
List<EduSubject> twoSubjectList = baseMapper.selectList(wrapperTwo);
//创建list集合,用于存储最终封装数据
List<OneSubject> finalSubjectList = new ArrayList<>();
//3 封装一级分类
//查询出来的所有的一级分类List集合遍历,得到每个一级分类对象,获取每个一级分类对象值
for (int i = 0; i < oneSubjectList.size(); i++) { //遍历oneSubjectList集合
EduSubject eduSubject = oneSubjectList.get(i);
//把eduSubject里面值获取出来,放到OneSubject对象里面
OneSubject oneSubject = new OneSubject();
// oneSubject.setId(eduSubject.getId());
// oneSubject.setTitle(eduSubject.getTitle());
//等价于上面两行代码
//eduSubject值复制到对应oneSubject对象里面
BeanUtils.copyProperties(eduSubject,oneSubject);
//多个OneSubject放到FinalSubjectList里面
finalSubjectList.add(oneSubject);
//在一级分类循环遍历查询所有的二级分类
//创建list集合封装每个一级分类的二级分类
List<TwoSubject> twoFinalSubjectList = new ArrayList<>();
//遍历二级分类list集合
for (int m = 0; m < twoSubjectList.size(); m++) {
//获取每个二级分类
EduSubject tSubject = twoSubjectList.get(m);
//判断二级分类的parentId和一级分类id是否意义
if (tSubject.getParentId().equals(eduSubject.getId())){
//把tSubject值复制到TwoSubject里面,放到twoFinalSubjectList里面
TwoSubject twoSubject = new TwoSubject();
BeanUtils.copyProperties(tSubject,twoSubject);
twoFinalSubjectList.add(twoSubject);
}
}
//把一级下面所有二级分类放到一级分类里面
oneSubject.setChildren(twoFinalSubjectList);
}
//4 封装二级分类
return finalSubjectList;
}
4、前端
课程相关表的关系:
edu_course:课程表,存储课程基本信息
edu_course_description:课程简介表,存储课程简介信息
edu_chapter:课程章节表,存储课程章节信息
edu_video:课程小节表,存储章节里面小节信息
edu_teacher:讲师表