预约挂号系统技术点详解(一)
一、Swagger2介绍与集成
1. swagger2介绍
⑴ 什么是swagger2
编写和维护接口文档是每个程序员的职责,根据Swagger2可以快速帮助我们编写最新的API接口文档,再也不用担心开会前仍忙于整理各种资料了,间接提升了团队开发的沟通效率。
⑵ 常用注解
swagger通过注解表明该接口会生成文档,包括接口名、请求方法、参数、返回信息的等等。
@Api:修饰整个类,描述Controller的作用
@ApiOperation:描述一个类的一个方法,或者说一个接口
@ApiParam:单个参数描述
@ApiModel:用对象来接收参数
@ApiModelProperty:用对象接收参数时,描述对象的一个字段
@ApiImplicitParam:一个请求参数
@ApiImplicitParams:多个请求参数
2. swagger2集成
⑴ 项目整合swagger2
在common模块pom.xml引入依赖
<!--swagger-->
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId>
</dependency>
⑵ 添加swagger2配置类
@Configuration
@EnableSwagger2
public class Swagger2Config {@Beanpublic Docket webApiConfig(){return new Docket(DocumentationType.SWAGGER_2).groupName("webApi").apiInfo(webApiInfo()).select()//只显示api路径下的页面.paths(Predicates.and(PathSelectors.regex("/api/.*"))).build();}@Beanpublic Docket adminApiConfig(){return new Docket(DocumentationType.SWAGGER_2).groupName("adminApi").apiInfo(adminApiInfo()).select()//只显示admin路径下的页面.paths(Predicates.and(PathSelectors.regex("/admin/.*"))).build();}private ApiInfo webApiInfo(){return new ApiInfoBuilder().title("网站-API文档").description("本文档描述了网站微服务接口定义").version("1.0").contact(new Contact("kejizhentan", "http://kejizhentan.com", "1213735556@qq.com")).build();}private ApiInfo adminApiInfo(){return new ApiInfoBuilder().title("后台管理系统-API文档").description("本文档描述了后台管理系统微服务接口定义").version("1.0").contact(new Contact("kejizhentan", "http://kejizhentan.com", "5656564@qq.com")).build();}
}
2. 单体项目中集成swagger2
⑴ 引入swagger依赖
<!--swagger2-->
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.7.0</version>
</dependency>
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.7.0</version>
</dependency>
⑵ 在主程序类名上使用@EnableSwagger注解启用
@EnableSwagger2
@SpringBootApplication
@MapperScan("com.kejizhentan.demo.mapper")
public class MybatisPlusProjectApplication {public static void main(String[] args) {SpringApplication.run(MybatisPlusProjectApplication.class, args);}}
⑶ 启动项目 访问swagger首页
访问:http://localhost:8080/swagger-ui.html
3. 微服务集成集成swagger2
⑴ 引入Swagger2依赖
<!--swagger-->
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version>
</dependency>
<dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version>
</dependency>
⑵ 编写swgger2配置类代码
@Configuration
@EnableSwagger2
public class Swagger2Config {@Beanpublic Docket webApiConfig(){return new Docket(DocumentationType.SWAGGER_2).groupName("webApi").apiInfo(webApiInfo()).select()//只显示api路径下的页面.paths(Predicates.and(PathSelectors.regex("/api/.*"))).build();}@Beanpublic Docket adminApiConfig(){return new Docket(DocumentationType.SWAGGER_2).groupName("adminApi").apiInfo(adminApiInfo()).select()//只显示admin路径下的页面.paths(Predicates.and(PathSelectors.regex("/admin/.*"))).build();}private ApiInfo webApiInfo(){return new ApiInfoBuilder().title("网站-API文档").description("本文档描述了网站微服务接口定义").version("1.0").contact(new Contact("kejizhentan", "http://kejizhentan.com", "1213735556@qq.com")).build();}private ApiInfo adminApiInfo(){return new ApiInfoBuilder().title("后台管理系统-API文档").description("本文档描述了后台管理系统微服务接口定义").version("1.0").contact(new Contact("kejizhentan", "http://kejizhentan.com", "5656564@qq.com")).build();}
}
⑶ 在需要测试的模块中引入有swagger2的模块坐标
如下图所示,因为service文件下的子模块中要编写业务代码。进行业务测试。所以在service的pom.xml文件中引入有swagger模块的坐标
⑷ 使用swagger2测试
在service-hosp主程序上添加需要扫描swgger2配置类的注解,并启动主程序
输入网址:http://localhost:8201/swagger-ui.html
二、返回结果封装方式
1. 新建返回值枚举类
ResultCodeEnum.java
/*** 统一返回结果状态信息类*/
@Getter
public enum ResultCodeEnum {SUCCESS(200,"成功"),FAIL(201, "失败"),PARAM_ERROR( 202, "参数不正确"),SERVICE_ERROR(203, "服务异常"),DATA_ERROR(204, "数据异常"),DATA_UPDATE_ERROR(205, "数据版本异常"),LOGIN_AUTH(208, "未登陆"),PERMISSION(209, "没有权限"),CODE_ERROR(210, "验证码错误"),
// LOGIN_MOBLE_ERROR(211, "账号不正确"),LOGIN_DISABLED_ERROR(212, "改用户已被禁用"),REGISTER_MOBLE_ERROR(213, "手机号已被使用"),LOGIN_AURH(214, "需要登录"),LOGIN_ACL(215, "没有权限"),URL_ENCODE_ERROR( 216, "URL编码失败"),ILLEGAL_CALLBACK_REQUEST_ERROR( 217, "非法回调请求"),FETCH_ACCESSTOKEN_FAILD( 218, "获取accessToken失败"),FETCH_USERINFO_ERROR( 219, "获取用户信息失败"),//LOGIN_ERROR( 23005, "登录失败"),PAY_RUN(220, "支付中"),CANCEL_ORDER_FAIL(225, "取消订单失败"),CANCEL_ORDER_NO(225, "不能取消预约"),HOSCODE_EXIST(230, "医院编号已经存在"),NUMBER_NO(240, "可预约号不足"),TIME_NO(250, "当前时间不可以预约"),SIGN_ERROR(300, "签名错误"),HOSPITAL_OPEN(310, "医院未开通,暂时不能访问"),HOSPITAL_LOCK(320, "医院被锁定,暂时不能访问"),;private Integer code;private String message;private ResultCodeEnum(Integer code, String message) {this.code = code;this.message = message;}
}
Result.java
/*** 全局统一返回结果类*/
@Data
@ApiModel(value = "全局统一返回结果")
public class Result<T> {@ApiModelProperty(value = "返回码")private Integer code;@ApiModelProperty(value = "返回消息")private String message;@ApiModelProperty(value = "返回数据")private T data;public Result(){}protected static <T> Result<T> build(T data) {Result<T> result = new Result<T>();if (data != null)result.setData(data);return result;}public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {Result<T> result = build(body);result.setCode(resultCodeEnum.getCode());result.setMessage(resultCodeEnum.getMessage());return result;}public static <T> Result<T> build(Integer code, String message) {Result<T> result = build(null);result.setCode(code);result.setMessage(message);return result;}public static<T> Result<T> ok(){return Result.ok(null);}/*** 操作成功* @param data* @param <T>* @return*/public static<T> Result<T> ok(T data){Result<T> result = build(data);return build(data, ResultCodeEnum.SUCCESS);}public static<T> Result<T> fail(){return Result.fail(null);}/*** 操作失败* @param data* @param <T>* @return*/public static<T> Result<T> fail(T data){Result<T> result = build(data);return build(data, ResultCodeEnum.FAIL);}public Result<T> message(String msg){this.setMessage(msg);return this;}public Result<T> code(Integer code){this.setCode(code);return this;}public boolean isOk() {if(this.getCode().intValue() == ResultCodeEnum.SUCCESS.getCode().intValue()) {return true;}return false;}
}
2. 在需要测试的模块中引入有返回结果封装枚举的模块坐标
3. 使用封装好的返回值.
三、全局异常处理和自定义异常类
spring boot 默认情况下会映射到 /error 进行异常处理,但是提示并不十分友好,下面自定义异常处理,提供友好展示。
1. 添加全局统一异常处理类
在service-util模块添加全局异常处理类GlobalExceptionHandler
/*** @Auther: kejizhentan* @Date 2022/11/20 21:13* @Description: 全局异常处理*/
@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)@ResponseBodypublic Result error(Exception e) {e.printStackTrace();return Result.fail();}/*** 自定义异常处理方法* @param e* @return*/@ExceptionHandler(YyghException.class)@ResponseBodypublic Result error(YyghException e) {return Result.build(e.getCode(), e.getMessage());}}
2. 自定义异常类YyghException
*** @Auther: kejizhentan* @Date 2022/11/20 21:13* @Description: 自定义异常类*/
@Data
@ApiModel(value = "自定义全局异常类")
public class YyghException extends RuntimeException {@ApiModelProperty(value = "异常状态码")private Integer code;/*** 通过状态码和错误消息创建异常对象* @param message* @param code*/public YyghException(String message, Integer code) {super(message);this.code = code;}/*** 接收枚举类型对象* @param resultCodeEnum*/public YyghException(ResultCodeEnum resultCodeEnum) {super(resultCodeEnum.getMessage());this.code = resultCodeEnum.getCode();}@Overridepublic String toString() {return "YyghException{" +"code=" + code +", message=" + this.getMessage() +'}';}
}
自定异常需要手动抛出才能生效
测试结果如下:
四、日志
1. 配置日志级别
日志记录器(Logger)的行为是分等级的。如下表所示:
分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL (日志级别依次递增)
默认情况下,spring boot从控制台打印出来的日志级别只有INFO及以上级别,可以配置日志级别
在application.properties
文件中配置日志级别
logging.level.root=WARN
这种方式只能将日志打印在控制台上
2. Logback日志
⑴ Logback日志介绍
spring boot内部使用Logback作为日志实现的框架。
Logback和log4j非常相似,如果你对log4j很熟悉,那对logback很快就会得心应手。
⑵ 配置日志
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:/yygh_log/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>
再次执行项目会在D盘生成日志文件
五、前端开发与开发工具介绍
1. 前端开发
前端工程师“Front-End-Developer”源自于美国。大约从2005年开始正式的前端工程师角色被行业所认可,到了2010年,互联网开始全面进入移动时代,前端开发的工作越来越重要。
最初所有的开发工作都是由后端工程师完成的,随着业务越来越繁杂,工作量变大,于是我们将项目中的可视化部分和一部分交互功能的开发工作剥离出来,形成了前端开发。
2. vscode安装和使用
⑴ 下载地址
https://code.visualstudio.com/
安装步骤安装:一个劲下一步就行
⑵ 插件安装
为方便后续开发,建议安装如下插件
⑶ 设置字体大小
左边栏 管理-> 设置-> 搜索 “font” -> Font size
⑷ 开启完整的Emmet语法支持
设置中搜索 Emmet:启用如下选项,必要时重启vs
⑸ 创建项目
vscode本身没有新建项目的选项,所以要先创建一个空的文件夹,如project_xxxx。
然后打开vscode,再在vscode里面选择 File -> Open Folder 打开文件夹
,这样才可以创建项目。
⑹ 保存工作区
打开文件夹后,选择“文件 -> 将工作区另存为…”,为工作区文件起一个名字,存储在刚才的文件夹下即可
⑺ 新建文件夹和网页
3. ECMAScript 6
⑴ ECMAScript 6
1) 什么是 ECMAScript 6
ECMAScript 6.0(简称 ES6)是 JavaScript 语言的下一代标准, 2015 年 6 月正式发布。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
2) ECMAScript 和 JavaScript 的关系
一个常见的问题是,ECMAScript 和 JavaScript 到底是什么关系?
要讲清楚这个问题,需要回顾历史。1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。
因此,ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 Jscript 和 ActionScript)
⑵ 基本语法
ES6相对之前的版本语法更严格,新增了面向对象的很多特性以及一些高级特性。本部分只学习项目开发中涉及到ES6的最少必要知识,方便项目开发中对代码的理解。
1) let声明变量
// var 声明的变量没有局部作用域
// let 声明的变量 有局部作用域
{var a = 1;let b = 2;
}
console.log(a)
console.log(b) //b is not defined 代码块中定义的变量只能作用于代码块中
// var 可以声明多次
// let 只能声明一次
var m = 1;
var m = 2;
let n = 10;
let n = 20; //'n' has already been declared
console.log(m)
console.log(n)
2) const声明常量(只读变量)
// 1、声明之后不允许改变
const PI = "3.1415926"
PI = 3 // TypeError: Assignment to constant variable.
// 2、一但声明必须初始化,否则会报错
const MY_AGE // Missing initializer in const declaration
3) 解构赋值
解构赋值是对赋值运算符的扩展。
他是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。
//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)
/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)
4) 模板字符串
模板字符串相当于加强版的字符串,用反引号`,除了作为普通字符串,还可以用来定义多行字符串,还可以在字符串中加入变量和表达式。
//字符串插入变量和表达式。变量名写在 ${} 中,${} 中可以放入 JavaScript 表达式。
let name = "lucy"
let age = 20
let info = `My name is ${name} ,I am ${age+1}`
console.log(info)
5) 声明对象简写
//传统方式定义对象
const name = "lucy"
const age = 20
const user1 = {name:name,age:age}
//console.log(user1)
//es6
const user2 = {name,age}
console.log(user2)
6) 对象拓展运算符
拓展运算符(...)
用于取出参数对象所有可遍历属性然后拷贝到当前对象。
//对象复制
let person1 = {name: "Amy", age: 15}
let someone1 = { ...person1}
//console.log(someone1)//对象合并
let age = {age: 15}
let name = {name: "Amy"}
let person2 = {...age, ...name}
console.log(person2)
7) 箭头函数
箭头函数提供了一种更加简洁的函数书写方式。基本语法是:
参数 => 函数体
箭头函数多用于匿名函数的定义
//传统方式定义函数
var f1 = function(a) {return a
}
//console.log(f1(3))//es6使用箭头函数定义
//参数 => 函数体
var f2 = a => a
//console.log(f2(4))
使用箭头函数
// 当箭头函数没有参数或者有多个参数,要用 () 括起来。
// 当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,
// 当只有一行语句,并且需要返回结果时,可以省略 {} , 结果会自动返回。
var f3 = function(m,n) {return m+n
}//es6
var f4 = (m,n) => m+n
console.log(f4(4,5))
4. vue入门(重要)
⑴ 介绍
1) Vue.js 是什么
在为 AngularJS 工作之后,Vue 的作者尤雨溪开发出了这一框架。他声称自己的思路是提取 Angular 中为自己所喜欢的部分,构建出一款相当轻量的框架。Vue 最早发布于 2014 年 2 月。作者在 Hacker News、Echo JS 与 Reddit 的 javascript 版块发布了最早的版本。一天之内,Vue 就登上了这三个网站的首页。Vue 是 Github 上最受欢迎的开源项目之一。同时,在 JavaScript 框架/函数库中,Vue 所获得的星标数已超过 React,并高于 Backbone.js、Angular 2、jQuery 等项目。
Vue.js 是一款流行的 JavaScript 前端框架,目的是简化 Web 开发。Vue 所关注的核心是 MVC 模式中的视图层,同时,它也能方便地获取数据更新,实现视图与模型的交互。
官方网站:https://cn.vuejs.org
2) 初识Vue.js
① 创建文件夹vue
② 创建文件夹js,将vue.min.js引入文件夹
③ 创建 hello.html
<!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="js/vue.min.js"></script><div id="app"><!--插值表达式-->{{message}}</div><script>new Vue({el:"#app",data:{message:'hello vue'}})</script>
</body>
</html>
这就是声明式渲染
:Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统
这里的核心思想就是没有繁琐的DOM操作,例如jQuery中,我们需要先找到div节点,获取到DOM对象,然后进行一系列的节点操作
data: {message: 'Hello Vue!'
}
3) 创建代码片段
文件 => 首选项 => 用户片段 => 新建全局代码片段文件:
vue-html.code-snippets
{"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=\"js/vue.min.js\"></script>"," <script>"," new Vue({"," el: '#app',"," data: {"," $1"," }"," })"," </script>","</body>","","</html>",],"description": "my vue template in html"}
}
⑵ 基本语法
1) 单向数据绑定
<script>new Vue({el: '#app',data: {msg:'color:green;'}})
</script>
你看到的 v-bind
特性被称为指令。指令带有前缀 v-
除了使用插值表达式{{}}进行数据渲染,也可以使用v-bind
指令,它的简写冒号(:)
<div v-bind:style="msg">单向绑定</div>
<div :style="msg">单向绑定</div>
2) 双向数据绑定
<div id="app">{{keyword}}<br/><input type="text" :value="keyword"/><br/><input type="text" v-model="keyword"/>
</div>
<script src="vue.min.js"></script>
<script>new Vue({el: '#app',data: {keyword:'柯基侦探'}})
</script>
什么是双向数据绑定?
● 当数据发生变化的时候,视图也会跟着发生变化
● 数据模型发生了改变,会直接显示在页面上
● 当视图发生变化的时候,数据也会跟着同步变化
● 用户在页面上的修改,会自动同步到数据模型中去
3) 事件
使用v-on
进行数件处理,v-on:click
表示处理鼠标点击事件,事件调用的方法定义在vue
对象声明的 methods
节点中
<div id="app"><button v-on:click="show()">事件绑定1</button><button @click="show()">事件绑定2</button>
</div>
<script src="vue.min.js"></script>
<script>new Vue({el: '#app',methods: {show() {console.log("show.....")}}})
</script>
4)条件渲染
<div id="app"><input type="checkbox" v-model="ok"/><br/><div v-if="ok">选中了</div><div v-else>没有选中</div>
</div>
<script src="vue.min.js"></script>
<script>new Vue({el: '#app',data: {ok:false}})
</script>
5)列表渲染
<div id="app"><div v-for="user in userList"> {{user.name}} -- {{user.age}} </div><div v-for="(user,index) in userList">{{index}} -- {{user.name}} -- {{user.age}}</div>
</div>
<script src="vue.min.js"></script>
<script>new Vue({el: '#app',data: {userList:[ {"name":"lucy","age":20},{"name":"mary","age":30}]}})
</script>
6)实例生命周期
<div id="app">{{msg}}
</div>
<script src="vue.min.js"></script>
<script>new Vue({el: '#app',data: {msg:'hello'},created() { //在页面渲染之前执行debuggerconsole.log('created.....')},mounted() {//在页面渲染之后执行debuggerconsole.log('mounted.....')}})
</script>
5. axios(重要)
⑴ axios的作用
axios是独立于vue的一个项目,可以用于浏览器和node.js中发送ajax请求
⑵ axios实例
1) 复制js资源
vue.min.js
axios.min.js
2) 创建 demo.html
3) 创建user.json文件模拟后端传入的数据
{"code":200,"message":"成功","data":{"items":[{"name":"zhangsan","age":18},{"name":"lisi","age":20},{"name":"wangwu","age":22}]}
}
4) 引入js
<script src="vue.min.js"></script>
<script src="axios.min.js"></script>
5) 编写js
<div id="app"><table><tr v-for="user in userList"><td>{{user.name}}</td><td>{{user.age}}</td></tr></table>
</div>
<script src="vue.min.js"></script>
<script src="axios.min.js"></script>
<script>new Vue({el: '#app',data: {userList:[]},created() { //在页面渲染之前执行//调用方法,得到返回json数据this.getList()},methods:{getList() {//使用axios方式ajax请求axios.get("user.json").then(response => {//请求成功//console.log(response)this.userList = response.data.data.itemsconsole.log(this.userList)}) .catch(error => {console.log(error)}) //请求失败}}})
</script>
6. element-ui
element-ui 是饿了么前端出品的基于 Vue.js的 后台组件库,方便程序员进行页面快速布局和构建
官网: http://element-cn.eleme.io/#/zh-CN
官网做的像bootstrap一样,想用啥样式直接复制粘贴就行了
7. Node.js介绍
⑴ Node.js的概念
1) JavaScript引擎
浏览器的内核包括两部分核心:
- DOM渲染引擎
- JavaScript解析引擎
Chrome浏览器内置V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。
2) 什么是Node.js
脱离浏览器环境也可以运行JavaScript,只要有JavaScript引擎就可以。
Node.js是一个基于Chrome V8引擎的JavaScript运行环境:即Node.js内置了Chrome的V8 引擎,可以在Node.js环境中直接运行JavaScript程序。
在Node.js中写JavaScript和在Chrome浏览器中写JavaScript基本没有什么不一样。哪里不一样呢?
- Node.js没有浏览器API,即document,window的等。
- 加了许多Node.js 专属API,例如文件系统,进程,http功能。
3) Node.js有什么用
- 如果你想开发类似JavaWeb的简单的后端程序,那么学习Node.js是一个非常好的选择。
- 如果你想部署一些高性能的服务,那么学习Node.js也是一个非常好的选择。
- 通常他会被用来作一个BFF层,即 Backend For Frontend(服务于前端的后端),通俗的说是一个专门用于为前端业务提供数据的后端程序
⑵ BFF
1) BFF 解决什么问题
一个前端页面向 Service A、Service B 以及 Service C发送请求,不同的微服务返回的值用于渲染页面中不同的组件。此时,每次访问该页面都需要发送 3 个请求。我们需要一个服务来聚合Service A、Service B 以及 Service C响应的数据,这个服务层叫做BFF。
手机、平板端、PC机等用户终端都需要向每个Service,例如Service A发送请求。对于同一个功能,不同的终端需要的数据格式和内容会有不同。此时 Service A 的一个接口,不能同时满足三个客户端的不同需求。我们可以在Service A中开发三个接口,也可以增加一个数据裁剪服务,将数据按照不同终端的不同要求进行裁剪,这个服务层叫做BFF。
BFF层的作用是让前端有能力自由组装后台数据,减少大量的业务沟通成本,加快业务的迭代速度。
无论是数据聚合还是数据剪裁,这类程序的特点是不需要太强大的服务器运算能力,但是对程序的灵活性有较高的要求,这两个特点都正好和Node.js的优势相吻合。
2)什么是BFF
用户体验适配器
⑶ 安装
1) 下载
官网:https://nodejs.org/en/
中文网:http://nodejs.cn/
LTS:长期支持版本
Current:最新版
2)安装
双击安装 node-v10.14.2-x64.msi
3)查看版本
node -v
⑷ 快速入门
使用前端开发工具:VSCode
1) 控制台查询
创建 01.js
console.log('Hello Node.js')
① 执行方式一:
找到01.js文件位置通过cmd执行
② 执行方式二
打开命令行终端:
node 01-控制台程序.js
2)服务器端应用开发(了解)
创建 02.js
//引入http模块
const http = require('http');
//创建服务器
http.createServer(function (request, response) {// 发送 HTTP 头部
// HTTP 状态值: 200 : OK
// 内容类型: text/plain
response.writeHead(200, {'Content-Type': 'text/html'});
// 发送响应数据 "Hello World"
response.end('<h1>Hello Node.js Server</h1>');
}).listen(8888);
// 终端打印如下信息
console.log('Server running at http://127.0.0.1:8888/');
运行服务器程序
node 02.js
服务器启动成功后,在浏览器中输入:http://localhost:8888/ 查看webserver成功运行,并输出html页面
停止服务:ctrl + c
8. NPM包管理器
⑴ 简介(什么是NPM)
NPM全称Node Package Manager
,是Node.js包管理工具,是全球最大的模块生态系统,里面所有的模块都是开源免费的;也是Node.js的包管理工具,相当于前端的Maven 。
安装node默认就安装好了NPM
#在命令提示符输入 npm -v 可查看当前npm版本
npm-v
⑵ 使用npm管理项目
1) 创建npm_pro文件夹(文件夹名称可以任意)
2) 项目初始化
#建立一个空文件夹,在命令提示符进入该文件夹 执行命令初始化
npm init
#按照提示输入相关信息,如果是用默认值则直接回车即可。
#name: 项目名称
#version: 项目版本号
#description: 项目描述
#keywords: {Array}关键词,便于用户搜索到我们的项目
#最后会生成package.json文件,这个是包的配置文件,相当于maven的pom.xml
#我们之后也可以根据需要进行修改。
#如果想直接生成 package.json 文件,那么可以使用命令
npm init -y
3) 修改npm镜像
NPM官方的管理的包都是从 http://npmjs.com下载的,但是这个网站在国内速度很慢。
这里推荐使用淘宝 NPM 镜像 http://npm.taobao.org/ ,淘宝 NPM 镜像是一个完整 npmjs.com 镜像,同步频率目前为 10分钟一次,以保证尽量与官方服务同步。
设置镜像地址:
#经过下面的配置,以后所有的 npm install 都会经过淘宝的镜像地址下载
npm config set registry https://registry.npm.taobao.org
#查看npm配置信息
npm config list
4) npm install命令的使用
基本命令
#使用 npm install 安装依赖包的最新版,
#模块安装的位置:项目目录\node_modules
#同时package.json 文件中,依赖包会被添加到dependencies节点下,类似maven中的 <dependencies>
#默认参数:--save 简写 -S 将当前依赖保存在dependencies节点下npm install jquery
下载特定版本的依赖
#如果安装时想指定特定的版本
npm install jquery@2.1.x
下载开发依赖
#devDependencies节点:开发时的依赖包,项目打包到生产环境的时候不包含的依赖
#使用 -D参数将依赖添加到devDependencies节点
npm install --save-dev eslint
#或简写
npm i -D eslint
下载全局依赖
#全局安装
#Node.js全局安装的npm包和工具的位置:用户目录\AppData\Roaming\npm\node_modules
#一些命令行工具常使用全局安装的方式
npm install --global webpack
#或简写
npm install -g webpack
根据依赖下载安装包
#npm管理的项目在备份和传输的时候一般不携带node_modules文件夹
#安装会自动在项目目录下添加 package-lock.json文件,这个文件帮助锁定安装包的版本
npm install #根据package.json中的配置下载依赖,初始化项目
演示如下:
我们可以先将依赖包删掉,只保留package.json和package-lock.json
这样在开发的时候只要有package.json和package-lock.json文件,就能生成相应的依赖
5) 其它命令
#更新包(更新到最新版本)
npm update 包名
#全局更新
npm update -g 包名
#卸载包
npm uninstall 包名
#全局卸载
npm uninstall -g 包名
9. 前端的模块化
cc 模块化简介
1) 模块化产生的背景
随着网站逐渐变成"互联网应用程序",嵌入网页的Javascript代码越来越庞大,越来越复杂。
Javascript模块化编程,已经成为一个迫切的需求。理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。
2) 模块化规范
- CommonJS模块化规范(基于ES6语法之前)
- ES6模块化规范(使用ES6语法)
⑵ ES6模块化规范
1) 创建 modeldemo文件夹进行项目初始话,并在新建的src下创建0.1js和02.js
2)导出模块
修改 src/0.1js 文件
export default {lsit() {console.log('list...')},save() {console.log('save...')}}
3) 导入模块
修改 src/02.js文件
import user from './01.js'//引入01.js
//调用01.js中的方法
user.lsit()
user.save()
ES6使用 export 和 import 来导出、导入模块。
4) 运行程序
node 02.js
注意:这时的程序无法运行的,因为ES6的模块化无法在Node.js中执行,需要用Babel编辑成ES5后再执行。
⑶ 使用Babel转码
ES6的某些高级语法在浏览器环境甚至是Node.js环境中无法执行。
Babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行执行。
这意味着,你可以现在就用 ES6 编写程序,而不用担心现有环境是否支持。
1) 安装
Babel提供babel-cli工具,用于命令行转码。它的安装命令如下:
npm install -g babel-cli
#查看是否安装成功
babel --version
2)初始化项目
在modeldemo目录下初始化项目(如果项目已经初始化过了这步可以省略)
npm init -y
3)配置.babelrc
Babel的配置文件是.babelrc,存放在项目的根目录下,该文件用来设置转码规则和插件,presets字段设定转码规则
{"presets": ["es2015"],
"plugins": []
}
4) 安装转码器
在项目modeldemo目录中安装
npm install -D babel-preset-es2015
5)转码
新建个dist文件夹,将src下的文件转码后自动放入dist文件夹下
# 整个目录转码
# --out-dir 或 -d 参数指定输出目录
babel src -d dist
6)运行程序
node 02.js
⑷ 更多的方式
ES6模块化规范还有一些其他的语法格式,常见的另一种写法如下:
src/teacherApi.js:
export functiongetList() {console.log('获取讲师列表2')
}
export functionsave() {console.log('保存讲师2')
}
src/teacherComponent.js:
import {getList, save} from"./teacherApi.js"
getList()
save()
10. Webpack
⑴ 什么是Webpack
Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
从图中我们可以看出,Webpack 可以将多种静态资源 js、css、less 转换成一个静态文件,减少了页面的请求。
⑵ Webpack安装
1) 全局安装
npm install -g webpack webpack-cli
或
npm install -g webpack
npm install -g webpack-cli
2)安装后查看版本号
webpack -v
⑶ 创建项目
1) 创建 webpack_demo文件夹
2) 初始化项目
进入目录,执行命令
npm init -y
3) 全局安装Webpack并创建src文件夹
4) src下创建01.js
这里使用的是CommonJS模块化方式,这种方式不支持ES6的语法,所以不需要Babel转码
exports.info=function (str) {document.write(str)
}
5) src下创建02.js
exports.add=function (a, b) {return a+b
}
6) src下创建main.js,将0.1.js和02.js文件引入到main.js文件中,并调用二者中的方法
const a=require('./0.1.js')
const b=require('./02.js')
a.info('Hello world!'+b.add(100, 200))
⑷ JS打包
1) 创建配置文件
webpack_demo目录下创建固定名称的
配置文件webpack.config.js
并创建打包输出文件夹dist
以下配置的意思是:
读取当前项目目录下src文件夹中的main.js(入口文件)内容,分析资源依赖,把相关的js文件打包
打包后的文件放入当前目录的dist文件夹下
打包后的js文件名为bundle.js
const path=require("path") //Node.js内置模块
module.exports= {entry: './src/main.js', //配置入口文件output: {path: path.resolve(__dirname, './dist'), //输出路径,__dirname:当前文件所在路径filename: 'bundle.js'//输出文件}
}
2) 执行编译命令
webpack --mode=development
#执行后查看bundle.js 里面包含了上面两个js文件的内容并进行了代码打包
3 ) 扩展: 2)中的打包方式也可以配置项目的npm运行命令,修改package.json文件
"scripts": {//...,"dev": "webpack --mode=development","prod": "webpack --mode=production"}
运行npm命令执行打包
npm run dev #开发打包
或
npm run prod #生产打包
3) 创建入口页面
webpack_demo目录下创建index.html,引用bundle.js
<scriptsrc="dist/bundle.js"></script>
4) 测试
浏览器中查看index.html
⑸ CSS打包
1) 安装插件
Webpack 本身只能处理 JavaScript 模块,如果要处理其他类型的文件,就需要使用 loader 进行转换。
loader 可以理解为是模块和资源的转换器。
首先我们需要安装相关loader插件,css-loader 是将 css 装载到 javascript;style-loader 是让 javascript 认识css
npm install -D style-loader css-loader
2) 修改webpack.config.js
const path=require("path") //Node.js内置模块
module.exports= {//...,output: {//其他配置},module: {rules: [ { test: /\.css$/, //打包规则应用到以css结尾的文件上use: ['style-loader', 'css-loader']} ] }
}
3) 在src文件夹创建style.css
body{background:pink;
}
4) 修改main.js
在main.js引入style.css
require('./style.css')
5) 运行打包编译命令
webpack --mode=development
6) 测试
浏览器中查看index.html,看看背景是不是变成粉色啦?
11. 前端框架vue-element-admin搭建介绍
⑴ 简介
vue-element-admin是基于element-ui 的一套后台管理系统集成方案。
GitHub地址:https://github.com/PanJiaChen/vue-element-admin
项目在线预览:https://panjiachen.gitee.io/vue-element-admin
⑵ 安装
可以点击直接下载前端模板https://kejizhentan.lanzoue.com/iahcv0gnhdwh
1 )解压压缩包,放到工作区目录下
2)根据package.json下载需要的依赖
# 安装依赖
npm install
注意:如果上面的install报错
则先执行下面的命令,再install
npm i -g node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
# 启动。执行后,浏览器自动弹出并访问http://localhost:9527/
npm run dev
⑶ 前端框架了解
1) 目录结构介绍
vue-element-admin-master(拥有完整的功能的插件)
2) 关键文件
vue-admin-template-master(源码相对简单,我们的后台管理系统基于这个版本)
① package.json
npm项目的核心配置文件,包含项目信息
,项目依赖
,项目启动相关脚本
启动项目的命令: npm run dev
dev脚本:webpack-dev-server --inline --progress --config build/webpack.dev.conf.js
webpack-dev-server
:一个小型的基于Node.js的http服务器,可以运行前端项目
--inline
:一种启动模式
--progress
:显示启动进度
--config build/webpack.dev.conf.js
:指定webpack配置文件所在位置
② build/webpack.dev.conf.js
webpack配置文件webpack.dev.conf.js
,包含项目在开发环境打包和运行的相关配置
webpack.dev.conf.js
中引用了 webpack.base.conf.js
webpack.base.conf.js
中定义了项目打包的入口文件
在HtmlWebpackPlugin配置html模板,生成的js就会自动插入到模板中,如下面的配置。
因此生成的js文件会被自动插入到名为index.html的页面中
③ index.html
项目默认的html页面
④ src/main.js
项目js入口文件,项目的所有前端功能都在这个文件中引入和定义,并初始化全局的Vue对象
⑤ config/dev.env.js
定义全局常量值
因此,在项目中的任意位置可以直接使用 process.env.BASE_API 常量表示后端接口的主机地址
⑥ src/utils/request.js
引入axios模块,定义全局的axios实例,并导出模块
⑦ src/api/login.js
引用request模块,调用远程api
⑷ 临时登录接口改造
1)前端登录问题
默认情况下,前端项目已经实现了登录功能,后端连接到远程Mock平台的模拟数据接口进行登录,而Mock平台地址无效,导致前端的登录功能无法执行
2)登录问题改造,使其能正常登录
① 修改默认使用useEslint插件
② 修改\src\store\modules\user.js
文件,将登陆相关请求接口改为静态数据,不请求接口
actions: {// 登录Login({ commit }, userInfo) {const data = {'token':'admin'}setToken(data.token)commit('SET_TOKEN', data.token)// const username = userInfo.username.trim()// return new Promise((resolve, reject) => {// login(username, userInfo.password).then(response => {// const data = response.data// setToken(data.token)// commit('SET_TOKEN', data.token)// resolve()// }).catch(error => {// reject(error)// })// })},// 获取用户信息GetInfo({ commit, state }) {const data = {'roles':'admin','name':'admin','avatar':'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif'}if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组commit('SET_ROLES', data.roles)} else {reject('getInfo: roles must be a non-null array !')}commit('SET_NAME', data.name)commit('SET_AVATAR', data.avatar)// return new Promise((resolve, reject) => {// getInfo(state.token).then(response => {// const data = response.data// if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组// commit('SET_ROLES', data.roles)// } else {// reject('getInfo: roles must be a non-null array !')// }// commit('SET_NAME', data.name)// commit('SET_AVATAR', data.avatar)// resolve(response)// }).catch(error => {// reject(error)// })// })},// 登出LogOut({ commit, state }) {commit('SET_TOKEN', '')commit('SET_ROLES', [])removeToken()// return new Promise((resolve, reject) => {// logout(state.token).then(() => {// commit('SET_TOKEN', '')// commit('SET_ROLES', [])// removeToken()// resolve()// }).catch(error => {// reject(error)// })// })},// 前端 登出FedLogOut({ commit }) {commit('SET_TOKEN', '')removeToken()resolve()// return new Promise(resolve => {// commit('SET_TOKEN', '')// removeToken()// resolve()// })}}
}
说明:token是我们手动配置的,如果过期后续自行生成,后续会有TokenHelper类生成token,在此不用关注
③ 需改\src\utils\request.js
config.headers['token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
④ 重启服务就能正常登录了
npm run dev
登录成功了
⑸ 前端框架vue-element-admin开发流程
开发流程中第三步的详细步骤
12. 预约挂号管理系统搭建过程(以医院设置管理为例)
⑴ 修改package.json来修改项目信息
{"name": "hospital-vue","version": "3.8.0","license": "MIT","description": "医院预约挂号管理系统","author": "kejizhentan <1213968@qq.com>",...
}
⑵ 如果需要修改端口号
config/index.js中修改
⑶ 项目的目录结构
⑷ 页面零星修改
1)标题
index.html(项目的html入口)
<title>柯基预约挂号管理平台系统</title>
2)国际化设置
打开 src/main.js
(项目的js入口),第7行,修改语言为 zh-CN,使用中文语言环境,例如:日期时间组件
import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n
3) 导航栏文字
src/views/layout/components(当前项目的布局组件)
src/views/layout/components/Navbar.vue
13行
<el-dropdown-item>首页
</el-dropdown-item>
17行
<span style="display:block;" @click="logout">退出</span>
4) 面包屑文字
src/components(可以在很多项目中复用的通用组件)
src/components/Breadcrumb/index.vue
38行
meta: { title: '首页' }
5)关闭校验
打开 config/index.js,配置是否开启语法检查
useEslint: false,
6)复制icon图标
将vue-element-admin/src/icons/svg
中的图标复制到 guli-admin项目中
7) 路由分析
① 入口文件中调用路由
src/main.js
...
import router from './router'//引入路由模块
...
new Vue({el: '#app',router,store,render: h => h(App)
})
② 路由模块中定义路由
src/router/index.js
...
export const constantRouterMap = [
...{ path: '*', redirect: '/404', hidden: true }
]export default new Router({// mode: 'history', //后端支持可开
scrollBehavior: () => ({ y: 0 }),
routes: constantRouterMap
})
说明:我们后续菜单路由就在此配置
⑸ 医院设置管理
1) 项目开发流程
① 定义路由模块
src/router/index.js配置医院设置管理相关路由
{path: '/hospSet',component: Layout,redirect: '/hospSet/table',name: '医院设置管理',meta: { title: '医院设置管理', icon: 'example' },children: [{path: 'table',name: '医院设置列表',component: () => import('@/views/hospset/list'),meta: { title: '医院设置列表', icon: 'table' }},{path: 'add',name: '医院设置添加',component: () => import('@/views/hospset/add'),meta: { title: '医院设置添加', icon: 'tree' }}]},
② 定义api模块
创建文件 src/api/hospset.js
import request from '@/utils/request'const api_name = '/admin/hosp/hospitalSet/findPageHospSet'export default{getHospSetList(current,limit,searchObj){return request({url: `${api_name}/${current}/${limit}`,method: 'post',data: searchObj //使用json})}
}
③ 定义页面组件脚本
src/views/hospset/list.vue
<template><div class="app-container">医院设置的列表</div>
</template>
<script>//引入接口定义的js文件import hospsetapi from '@/api/hospset'export default {// 定义数据模型,如定义变量和初始值data() {return {current:1, //当前页limit:3, //每页显示记录数searchObj:{}, //条件封装对象list: [] // 每页数据集合}},// 在页面渲染之前执行created() {//一般调用methods中定义的方法得到数据this.getList()},//定义方法进行请求接口调用methods: {// 医院设置列表getList() {hospsetapi.getHospSetList(this.current, this.limit, this.searchObj).then(response => {//请求成功response是接口返回的数据console.log(response)//this.list = response.data.records})//请求成功调用 .catch(error =>{console.log(error)})//请求失败}}}
</script>
④ 修改请求的路径
修改/config/dev.env.js中的配置
⑤ 测试数据通信
启动项目
命令行执行:npm run dev
打开浏览器调试状态
测试失败,接口请求了,但是没有返回结果,这是为什么呢?其实这就是跨域的问题,
⑥ 跨域处理
跨域:浏览器对于javascript的同源策略的限制 。
以下情况都属于跨域:
如果域名和端口都相同,但是请求路径不同,不属于跨域,如:
www.jd.com/item
www.jd.com/goods
http和https也属于跨域
而我们刚才是从localhost:9531去访问localhost:8201,这属于端口不同,跨域了。
如何解决呢?
Spring早就给我们提供了解决方案,我们只需要在对应controller上添加一个标签就可以了(@CrossOrigin
//跨域)。
我们在HospitalSetController
类上添加跨域标签@CrossOrigin
,再进行测试,则测试成功!
⑦ 修改/src/utils/request.js配置的默认状态码,使其和后端配置的返回值状态码一致
再次测试则数据能正常获取
⑧ 修改src/views/hospset/list.vue文件将返回的值赋值给集合list并修改定义页面组件模板
<template><div class="app-container">医院设置的列表<!-- banner列表 --><el-table:data="list"stripestyle="width: 100%"><el-table-column type="index" width="50" label="序号"/><el-table-column prop="hosname" label="医院名称"/><el-table-column prop="hoscode" label="医院编号"/><el-table-column prop="apiUrl" label="api基础路径" width="200"/><el-table-column prop="contactsName" label="联系人姓名"/><el-table-column prop="contactsPhone" label="联系人手机"/><el-table-column label="状态" width="80"><template slot-scope="scope">{{ scope.row.status === 1 ? '可用' : '不可用' }}</template></el-table-column></el-table></div>
</template>
<script>//引入接口定义的js文件import hospsetapi from '@/api/hospset'export default {// 定义数据模型,如定义变量和初始值data() {return {current:1, //当前页limit:3, //每页显示记录数searchObj:{}, //条件封装对象list: [], // 每页数据集合total:0}},// 在页面渲染之前执行created() {//一般调用methods中定义的方法得到数据this.getList()},//定义方法进行请求接口调用methods: {// 医院设置列表getList() {hospsetapi.getHospSetList(this.current, this.limit, this.searchObj).then(response => {//请求成功response是接口返回的数据//console.log(response)//将返回的值赋值给集合listthis.list = response.data.records//将返回的总记录数total值赋值给totalthis.total = response.data.total})//请求成功调用 .catch(error =>{console.log(error)})//请求失败}}}
</script>
修改后的页面效果如下:
2) 分页和表单查询
① 添加分页组件、表单查询组件并添加当前页的参数
<template><div class="app-container">医院设置的列表<el-form :inline="true" class="demo-form-inline"><el-form-item><el-input v-model="searchObj.hosname" placeholder="医院名称"/></el-form-item><el-form-item><el-input v-model="searchObj.hoscode" placeholder="医院编号"/></el-form-item><el-button type="primary" icon="el-icon-search" @click="getList()">查询</el-button></el-form><!-- banner列表 --><el-table:data="list"stripestyle="width: 100%"><el-table-column type="index" width="50" label="序号"/><el-table-column prop="hosname" label="医院名称"/><el-table-column prop="hoscode" label="医院编号"/><el-table-column prop="apiUrl" label="api基础路径" width="200"/><el-table-column prop="contactsName" label="联系人姓名"/><el-table-column prop="contactsPhone" label="联系人手机"/><el-table-column label="状态" width="80"><template slot-scope="scope">{{ scope.row.status === 1 ? '可用' : '不可用' }}</template></el-table-column></el-table><!-- 分页 --><el-pagination:current-page="current":page-size="limit":total="total"style="padding: 30px 0; text-align: center;"layout="total, prev, pager, next, jumper"@current-change="getList"/></div>
</template>
<script>//引入接口定义的js文件import hospsetapi from '@/api/hospset'export default {// 定义数据模型,如定义变量和初始值data() {return {current:1, //当前页limit:3, //每页显示记录数searchObj:{}, //条件封装对象list: [], // 每页数据集合total:0}},// 在页面渲染之前执行created() {//一般调用methods中定义的方法得到数据this.getList()},//定义方法进行请求接口调用methods: {// 医院设置列表getList(page = 1) {//添加当前页参数this.current = pagehospsetapi.getHospSetList(this.current, this.limit, this.searchObj).then(response => {//请求成功response是接口返回的数据//console.log(response)//将返回的值赋值给集合listthis.list = response.data.records//将返回的总记录数total值赋值给totalthis.total = response.data.total})//请求成功调用 .catch(error =>{console.log(error)})//请求失败}}}
</script>
② 优化后的效果
3) 删除功能实现
① 修改api模块
在src\api\hospset.js
添加方法
//删除医院设置
deleteHospSet(id) {return request ({url: `/admin/hosp/hospitalSet/${id}`,method: 'delete'})
}
② 修改页面组件模板
在table组件中添加删除列
<el-table-column label="操作" width="280" align="center"><template slot-scope="scope"><el-button type="danger" size="mini" icon="el-icon-delete" @click="removeDataById(scope.row.id)"> </el-button></template>
</el-table-column>
③ 修改页面组件脚本
//删除医院设置的方法
removeDataById(id) {this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning' }).then(() => {//确定执行的方法//调用接口hospsetapi.deleteHospSet(id).then(response=>{this.$message({type: 'success',message: '删除成功!'})//刷新页面this.getList(1)})})
}
④ 效果演示
4) 批量删除功能实现
① 修改api模块
在 \src\api\hospset.js
添加方法
/批量删除医院设置
batchHospSet(idList) {return request({url: `/admin/hosp/hospitalSet/batchRemove`,method: 'delete',data: idList})
}
② 修改页面组件模板
在table组件上方添加批量删除点击事件按钮
<!-- 工具条 -->
<div><el-button type="danger" size="mini" @click="removeRows()">批量删除</el-button>
</div>
在table组件上添加复选框
<el-table:data="list"stripestyle="width: 100%" @selection-change="handleSelectionChange"><el-table-column type="selection" width="55"/>
③ 定义页面组件脚本
Data定义数据
multipleSelection: [] // 批量选择中选择的记录列表
定义当表格复选框选项发生变化的时候触发方法
// 当表格复选框选项发生变化的时候触发
handleSelectionChange(selection) {//获取复选框的id值
this.multipleSelection = selection
},
定义删除方法
//批量删除医院设置的方法
removeRows(id) {this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning' }).then(() => {//确定执行的方法var idList = []//遍历数组得到每个id值,设置到idList里面for(var i=0;i<this.multipleSelection.length;i++) {var obj = this.multipleSelection[i]var id = obj.ididList.push(id)}//调用接口hospsetapi.batchHospSet(idList).then(response=>{this.$message({type: 'success',message: '删除成功!'})//刷新页面this.getList(1)})})
}
④ 效果如下
5) 锁定与取消锁定
① 定义api模块
在 src/api/hospset.js添加方法
//锁定和取消锁定
lockHospSet(id,status) {return request ({url: `/admin/hosp/hospitalSet/lockHospitalSet/${id}/${status}`,method: 'put'})
}
② 定义页面组件模板
在table组件上添加按钮
<el-button v-if="scope.row.status==1" type="primary" size="mini" icon="el-icon-delete" @click="lockHostSet(scope.row.id,0)">锁定</el-button>
<el-button v-if="scope.row.status==0" type="danger" size="mini" icon="el-icon-delete" @click="lockHostSet(scope.row.id,1)">取消锁定</el-button>
③ 定义页面组件脚本
//锁定和取消锁定
lockHostSet(id,status) {hospsetapi.lockHospSet(id,status).then(response => {//刷新this.getList()})
}
④ 效果如下
6) 添加医院设置
① 定义api模块
在 src/api/hospset.js添加方法
//添加医院设置
saveHospSet(hospitalSet) {return request ({url: `/admin/hosp/hospitalSet/saveHospitalSet`,method: 'post',data: hospitalSet})
}
② 定义页面组件模板
src/views/hospset/add.vue
<template><div class="app-container">医院设置新增<el-form label-width="120px"><el-form-item label="医院名称"><el-input v-model="hospitalSet.hosname"/></el-form-item><el-form-item label="医院编号"><el-input v-model="hospitalSet.hoscode"/></el-form-item><el-form-item label="api基础路径"><el-input v-model="hospitalSet.apiUrl"/></el-form-item><el-form-item label="联系人姓名"><el-input v-model="hospitalSet.contactsName"/></el-form-item><el-form-item label="联系人手机"><el-input v-model="hospitalSet.contactsPhone"/></el-form-item><el-form-item><el-button type="primary" @click="saveOrUpdate">保存</el-button></el-form-item></el-form></div>
</template>
③ 定义页面组件脚本
src/views/hospset/add.vue
<script>//引入接口定义的js文件import hospsetapi from '@/api/hospset'export default {// 定义数据模型,如定义变量和初始值data() {return {hospitalSet:{}}},// 在页面渲染之前执行created() {},//定义方法进行请求接口调用methods: {//添加saveOrUpdate() {hospsetapi.saveHospSet(this.hospitalSet).then(response => {//提示this.$message({type: 'success',message: '添加成功!'})//跳转列表页面,使用路由跳转方式实现this.$router.push({path:'/hospSet/list'})})}}}
</script>
④ 效果如下
7) 更新医院设置(更新和新增共用同一个页面)
① 更新回显示医院设置
■ 添加隐藏路由
在/src/router/index.js
添加隐藏路由
{path: 'edit/:id',name: 'EduTeacherEdit',component: () =>import('@/views/hospset/add'),meta: { title: '编辑', noCache: true },hidden: true
}
■ 定义api模块
在 /src/api/hospset.js添加方法
//设置id查询
getHospSet(id) {return request ({url: `/admin/hosp/hospitalSet/getHospSet/${id}`,method: 'get'})}
■ 定义页面组件模板
/src/views/hospset/list.vue
<router-link :to="'/hospSet/edit/'+scope.row.id"><el-button type="primary" size="mini" icon="el-icon-edit">修改</el-button>
</router-link>
■ 定义页面组件脚本
在/src/views/hospset/add.vue文件中methods中定义回显方法
//根据id查询
getHostSet(id) {hospsetapi.getHospSet(id) .then(response => {this.hospitalSet = response.data})
}
页面渲染成功后获取数据
因为已在路由中定义如下内容:path: 'edit/:id'
,因此可以使用 this.$route.params.id
获取路由中的id
// 在页面渲染之前执行
created() {//获取路由id值 调用接口得到医院设置信息if(this.$route.params && this.$route.params.id) {const id = this.$route.params.idthis.getHostSet(id)}},
■ 效果如下:
② 更新医院设置
■ 定义api模块
在/src/api/hospset.js
添加方法
//修改医院设置
updateHospSet(hospitalSet) {return request ({url: `/admin/hosp/hospitalSet/updateHospitalSet`,method: 'post',data: hospitalSet})}
■ 定义页面组件脚本
在/src/views/hospset/add.vue
methods中定义update
//修改
update() {hospsetapi.updateHospSet(this.hospitalSet).then(response => {//提示this.$message({type: 'success',message: '修改成功!'})//跳转列表页面,使用路由跳转方式实现this.$router.push({path:'/hospSet/list'})})
}
完善saveOrUpdate方法
//新增和修改
saveOrUpdate() {//判断添加还是修改if(!this.hospitalSet.id) { //没有id,做添加this.save();} else {//修改this.update()}
},
//修改
update() {hospsetapi.updateHospSet(this.hospitalSet).then(response => {//提示this.$message({type: 'success',message: '修改成功!'})//跳转列表页面,使用路由跳转方式实现this.$router.push({path:'/hospSet/list'})})
},//保存
save(){hospsetapi.saveHospSet(this.hospitalSet).then(response => {//提示this.$message({type: 'success',message: '添加成功!'})//跳转列表页面,使用路由跳转方式实现this.$router.push({path:'/hospSet/list'})})
}
■ 效果如下
8) 组件重用问题
在/src/views/hospset/add.vue
中修改页面渲染执行的方法,判断如果是新增的页面就清空表单中的数据
// 在页面渲染之前执行
created() {//获取路由id值 调用接口得到医院设置信息if(this.$route.params && this.$route.params.id) {const id = this.$route.params.idthis.getHostSet(id)}else{//否则说明是新增页面,则清空表单中的数据this.hospitalSet = {}}},
效果:点击完修改再点击新增页面跳转但输入框中的数据并未清空
问题:vue-router导航切换 时,如果两个路由都渲染同个组件,组件的生命周期方法(created或者mounted)不会再被调用, 组件会被重用,显示上一个路由渲染出来的自建。
解决方案:可以简单的在 router-view上加上一个唯一的key,来保证路由切换时都会重新触发生命周期方法,确保组件被重新初始化。
修改/src/views/layout/components/AppMain.vue
文件如下:
<router-view:key="key"></router-view>
computed: {key() {return this.$route.name !== undefined ? this.$route.name + +new Date() : this.$route + +new Date()}}
修改后的效果如下:
六、 element-ui前端树状结构列表显示的处理和EasyExcel的导入、导出
1、 页面效果
2、表设计
3、数据分析
parent_id:
上级id,通过id与parent_id构建上下级关系,例如:我们要获取所有行业数据,那么只需要查询parent_id=20000的数据
name:
名称,例如:填写用户信息,我们要select标签选择民族,“汉族”就是数据字典的名称
value:
值,例如:填写用户信息,我们要select标签选择民族,“1”(汉族的标识)就是数据字典的值
dict_code:
编码,编码是我们自定义的,全局唯一,例如:我们要获取行业数据,我们可以通过parent_id获取,但是parent_id是不确定的,所以我们可以根据编码来获取行业数据
说明:系统中会使用省市区三级联动数据,该数据我们来自“国家统计局”官方数据,地址:
http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2019/index.html
4、根据页面效果分析数据接口
数据字典是树形展示,由于数据众多,我们使用“树形数据与懒加载”的方式展现数据列表,其他就是对数据的新增、修改与删除操作,因此需要提供的接口如下:
1,根据上级id获取下级数据(构造树形数据),参考文档:https://element.eleme.cn/#/zh-CN/component/table,页面搜索:树形数据与懒加载
2,导入接口
3,导出接口
5、树状结构后端service-cmn模块搭建
⑴ 搭建service-cmn模块
⑵ 添加配置文件application.properties
# 服务端口
server.port=8202
# 服务名
spring.application.name=service-cmn# 环境设置:dev、test、prod
spring.profiles.active=dev# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/yygh_cmn?characterEncoding=utf-8&useSSL=false
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
⑶ ServiceCmnApplication.java启动类
@SpringBootApplication
@ComponentScan(basePackages = "com.kejizhentan")
public class ServiceCmnApplication {public static void main(String[] args) {SpringApplication.run(ServiceCmnApplication.class, args);}
}
⑷ 数据字典列表接口设计
根据element组件要求,返回列表数据必须包含hasChildren字典,如图:
https://element.eleme.cn/#/zh-CN/component/table
1) model模块添加数据字典实体
在model模块创建实体:com.kejizhentan.yygh.model.cmn.Dict.java
Dict.java
@Data
@ApiModel(description = "数据字典")
@TableName("dict")
public class Dict {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "id")private Long id;@ApiModelProperty(value = "创建时间")@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")@TableField("create_time")private Date createTime;@ApiModelProperty(value = "更新时间")@TableField("update_time")private Date updateTime;@ApiModelProperty(value = "逻辑删除(1:已删除,0:未删除)")@TableLogic@TableField("is_deleted")private Integer isDeleted;@ApiModelProperty(value = "其他参数")@TableField(exist = false)private Map<String,Object> param = new HashMap<>();@ApiModelProperty(value = "上级id")@TableField("parent_id")private Long parentId;@ApiModelProperty(value = "名称")@TableField("name")private String name;@ApiModelProperty(value = "值")@TableField("value")private String value;@ApiModelProperty(value = "编码")@TableField("dict_code")private String dictCode;@ApiModelProperty(value = "是否包含子节点")@TableField(exist = false)private boolean hasChildren;}
说明:hasChildren为树形组件所需字典,标识为数据库表不存在该字段
2) 添加数据字典mapper
新增com.kejizhentan.cmn.mapper.DictMapper.java
/*** @Entity com.kejizhentan.cmn.bean.Dict*/
public interface DictMapper extends BaseMapper<Dict> {}
3) 添加数据字典service
1、新增com.kejizhentan.cmn.service.DictService.java
public interface DictService extends IService<Dict> {List<Dict> findChlidData(Long id);
}
2、新增com.kejizhentan.cmn.service.impl.DictServiceImpl.java
接口实现
@Service
public class DictServiceImpl extends ServiceImpl<DictMapper, Dict>implements DictService{//根据数据id查询子数据列表@Overridepublic List<Dict> findChlidData(Long id) {QueryWrapper<Dict> wrapper = new QueryWrapper<>();wrapper.eq("parent_id",id);List<Dict> dictList = baseMapper.selectList(wrapper);//向list集合每个dict对象中设置hasChildrenfor (Dict dict:dictList) {Long dictId = dict.getId();boolean isChild = this.isChildren(dictId);dict.setHasChildren(isChild);}return dictList;}//判断id下面是否有子节点private boolean isChildren(Long id) {QueryWrapper<Dict> wrapper = new QueryWrapper<>();wrapper.eq("parent_id",id);Long count = baseMapper.selectCount(wrapper);// 0>0 1>0return count>0;}
}
4) 添加数据字典controller
添加src.main.java.com.kejizhentan.cmn.controller.DictController.java
@Api(description = "数据字典接口")
@RestController
@RequestMapping("/admin/cmn/dict")
public class DictController {@Autowiredprivate DictService dictService;//根据数据id查询子数据列表@ApiOperation(value = "根据数据id查询子数据列表")@GetMapping("findChildData/{id}")public Result findChildData(@PathVariable Long id) {List<Dict> list = dictService.findChlidData(id);return Result.ok(list);}
}
5) 接口列表如下
6、树状结构前端模块搭建
⑴ 添加路由
在 src/router/index.js
文件添加路由
{path: '/cmn',component: Layout,redirect: '/cmn/list',name: '数据字典管理',meta: { title: '数据字典管理', icon: 'example' },children: [{path: 'list',name: '数据字典管理',component: () => import('@/views/dict/list'),meta: { title: '数据字典管理', icon: 'table' }}]
},
⑵ 定义api
创建/src/api/dict.js
文件
import request from '@/utils/request'
export default{//获取医院设置列表dictList(id){return request({url: `/admin/cmn/dict/findChildData/${id}`,method: 'get'})}
}
⑶ 添加页面组件模板
/src/views/dict/list.vue
<template><div class="app-container"><el-table:data="list"style="width: 100%"row-key="id"borderlazy:load="getChildrens":tree-props="{children: 'children', hasChildren: 'hasChildren'}"><el-table-column label="名称" width="230" align="left"><template slot-scope="scope"><span>{{ scope.row.name }}</span></template></el-table-column><el-table-column label="编码" width="220"><template slot-scope="{row}">{{ row.dictCode }}</template></el-table-column><el-table-column label="值" width="230" align="left"><template slot-scope="scope"><span>{{ scope.row.value }}</span></template></el-table-column><el-table-column label="创建时间" align="center"><template slot-scope="scope"><span>{{ scope.row.createTime }}</span></template></el-table-column></el-table></div>
</template>
⑷ 添加页面组件脚本
<script>//引入接口定义的js文件import dictapi from '@/api/dict'export default {// 定义数据模型,如定义变量和初始值data() {return {list:[] //数据字典列表数组}},// 在页面渲染之前执行created() {//一般调用methods中定义的方法得到数据this.getDictList(1)},//定义方法进行请求接口调用methods: {//数据字典列表getDictList(id) {dictapi.dictList(id).then(response => {this.list = response.data})},getChildrens(tree, treeNode, resolve) {dictapi.dictList(tree.id).then(response => {resolve(response.data)})}}}
</script>
⑸ 通过更新element版本解决页面树状层级不显示问题
上面的页面并没有树状层级显示
1)删除/node_modules/element-ui文件夹
2)修改/package.json配置的element-ui版本为2.12.0并通过npm install
命令重新下载依赖
3)最后的效果如下
7、 EasyExcel介绍
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便。
EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。
文档地址:https://alibaba-easyexcel.github.io/index.html
github地址:https://github.com/alibaba/easyexcel
⑴ EasyExcel读写操作的简单演示
1) 引入依赖
<dependencies><!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel --><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.1.1</version></dependency>
</dependencies>
2) 创建实体并通过注解设置表头信息
@Data
public class UserData {@ExcelProperty("用户编号")private int uuid;@ExcelProperty("用户名称")private String username;
}
3) 通过main方法实现简单的写操作
public class WriteTest {public static void main(String[] args) {List<UserData> userList = new ArrayList<UserData>();for (int i = 0; i < 10; i++) {UserData userData = new UserData();userData.setUuid(i);userData.setUsername("zhangsan"+i);userList.add(userData);}//设置excel写入的文件路径和名称String fileName = "E:\\excel\\01.xlsx";//调用方法实现写操作EasyExcel.write(fileName,UserData.class).sheet("用户信息").doWrite(userList);System.out.println("excel写入成功");}
}
效果如下:
4) 通过main方法实现简单的读操作
① 修改实体中的@ExcelProperty
,设置属性对应的列的内容
@Data
public class UserData {@ExcelProperty(value="用户编号",index = 0)private int uuid;@ExcelProperty(value="用户名称",index=1)private String username;
}
② 创建监听器类一行一行地读取excel文件中的内容
public class ExcelLinster extends AnalysisEventListener<UserData> {//一行一行读取excel,从第二行读取@Overridepublic void invoke(UserData userData, AnalysisContext analysisContext) {System.out.println("excel表信息:"+userData);}//读取第一行表头信息@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {System.out.println("表头信息:"+headMap);}//读取之后执行@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {}
}
③ 在main方法中执行excel读取的操作
public class ReadTest {public static void main(String[] args) {//设置excel读取的文件路径和名称String fileName = "E:\\excel\\01.xlsx";//调用方法实现读操作EasyExcel.read(fileName,UserData.class,new ExcelLinster()).sheet().doRead();}
}
⑵ 数据字典导出
1)添加依赖
<dependencies><!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel --><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.1.1</version></dependency>
</dependencies>
2) 导出接口封装
① 在model模块添加导出实体
在model模块查看实体:com.kejizhentan.yygh.vo.cmn.DictEeVo.java
@Data
public class DictEeVo {@ExcelProperty(value = "id" ,index = 0)private Long id;@ExcelProperty(value = "上级id" ,index = 1)private Long parentId;@ExcelProperty(value = "名称" ,index = 2)private String name;@ExcelProperty(value = "值" ,index = 3)private String value;@ExcelProperty(value = "编码" ,index = 4)private String dictCode;}
② 在service-cmn模块添加service方法
1、在DictService类添加接口
/**
* @Auther: kejizhentan
* @Date 2022/11/27 12:23
* @Description: 字典导出
*/
void exportDict(HttpServletResponse response) throws Exception;
2、在DictServiceImpl类添加接口实现类
/*** @Auther: kejizhentan* @Date 2022/11/27 12:24* @Description: 数据字典导出*/
@Override
public void exportDict(HttpServletResponse response) throws Exception {// 设置下载信息response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");// 这里 URLEncoder.encode 可以防止中文乱码,和 EasyExcel 没有关系//String fileName = "dict";// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系String fileName = URLEncoder.encode("数据字典", "UTF-8");response.setHeader("Content-disposition", "attachment;filename="+ fileName + ".xlsx");// 查询数据库List<Dict> dictList = baseMapper.selectList(null);List<DictEeVo> dictVoList = new ArrayList<>(dictList.size());for (Dict dict : dictList) {DictEeVo dictVo = new DictEeVo();//dict-->dictvoBeanUtils.copyProperties(dict, dictVo);dictVoList.add(dictVo);}try {// 调用方法进行写操作EasyExcel.write(response.getOutputStream(), DictEeVo.class).sheet("dict").doWrite(dictVoList);} catch (IOException e) {throw new RuntimeException(e);}
}
③ 在service-cmn模块添加controller方法
在DictController类添加方法
/**
* @Auther: kejizhentan
* @Date 2022/11/27 12:26
* @Description: 导出数据字典
*/
@ApiOperation(value = "导出数据字典")
@GetMapping("/exportData")
public void exportDict(HttpServletResponse response) throws Exception {dictService.exportDict(response);
}
④ 测试
直接通过浏览器导出数据:http://localhost:8202/admin/cmn/dict/exportData
3) 导出前端实现
① 列表页面添加导出按钮
/src/views/dict/list.vue
<div class="el-toolbar"><div class="el-toolbar-body"style="justify-content: flex-start;"><el-button type="text"@click="exportData"><i class="fa fa-plus"/> 导出</el-button></div>
</div>
② 添加导出方法
/src/views/dict/list.vue
exportData() {window.location.href = 'http://localhost:8202/admin/cmn/dict/exportData'}
③ 测试
⑶ 数据字典导入
1) 导入接口封装
① 创建回调监听器
com.kejizhentan.cmn.listener.DictListener.java
@Component
public class DictListener extends AnalysisEventListener<DictEeVo> {//通过有参构造器传参的方式构建对象private DictMapper dictMapper;public DictListener(DictMapper dictMapper) {this.dictMapper = dictMapper;}//一行一行读取@Overridepublic void invoke(DictEeVo dictEeVo, AnalysisContext analysisContext) {//调用方法添加数据库Dict dict = new Dict();BeanUtils.copyProperties(dictEeVo,dict);dictMapper.insert(dict);}@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {}
}
② 在service-cmn模块添加service方法
1、在DictService类添加接口
/*** @Auther: kejizhentan* @Date 2022/11/27 13:06* @Description: 导入数据字典*/
void importDict(MultipartFile file);
2、在DictServiceImpl类添加接口实现类
/*** @Auther: kejizhentan* @Date 2022/11/27 13:07* @Description: 导入数据字典*/
@Override
public void importDict(MultipartFile file) {try {EasyExcel.read(file.getInputStream(), DictEeVo.class, new DictListener(baseMapper)).sheet().doRead();} catch (IOException e) {throw new RuntimeException(e);}
}
注意这里用了有参构造器传参的方式来注入对象
③ 在service-cmn模块添加controller方法
/*** @Auther: kejizhentan* @Date 2022/11/27 13:05* @Description: 导入数据字典*/
@ApiOperation(value = "导入数据字典")
@PostMapping("/importData")
public Result importDict(MultipartFile file) {dictService.importDict(file);return Result.ok();
}
2) 导入前端实现
① 列表页面添加导入按钮
/src/views/dict/list.vue
<el-button type="primary" @click="importData" >导入<i class="el-icon-upload el-icon--right"></i></el-button>
② 添加导入弹出层
<el-dialog title="导入" :visible.sync="dialogImportVisible" width="480px"><el-form label-position="right"label-width="170px"><el-form-item label="文件"><el-upload:multiple="false":on-success="onUploadSuccess":action="'http://localhost:8202/admin/cmn/dict/importData'"class="upload-demo"><el-button size="small"type="primary">点击上传</el-button><div slot="tip"class="el-upload__tip">只能上传excel文件,且不超过500kb</div></el-upload></el-form-item></el-form><div slot="footer"class="dialog-footer"><el-button @click="dialogImportVisible= false">取消</el-button></div>
</el-dialog>
③ 添加弹出可见模型
data() {return {list:[], //数据字典列表数组dialogImportVisible: false//设置弹窗是否弹出,false表示不弹出}
}
④ 添加方法脚本
//导入数据字典
importData() {this.dialogImportVisible = true
},
onUploadSuccess(response, file) {this.$message.info('上传成功')this.dialogImportVisible = falsethis.getDictList(1)//刷新页面
}
⑤ 测试效果如下
8、Spring Cache + Redis 缓存数据
Spring Cache 是一个非常优秀的缓存组件。自Spring 3.1起,提供了类似于@Transactional注解事务的注解Cache支持,且提供了Cache抽象,方便切换各种底层Cache(如:redis)
使用Spring Cache的好处:
1,提供基本的Cache抽象,方便切换各种底层Cache;
2,通过注解Cache可以实现类似于事务一样,缓存逻辑透明的应用到我们的业务代码上,且只需要更少的代码就可以完成;
3,提供事务回滚时也自动回滚缓存;
4,支持比较复杂的缓存逻辑;
⑴ 项目集成Spring Cache + Redis
因为缓存也是公共使用,所有的service模块都有可能使用缓存,所以我们把依赖与部分配置加在service-util模块,这样其他service模块都可以使用了
1) service-util添加依赖
在service-util模块的pom.xml添加依赖
<!-- 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>
2) service-util添加配置类
com.kejizhentan.config.RedisConfig.java
@Configuration
@EnableCaching
public class RedisConfig {/*** 自定义key规则** @return*/@Beanpublic KeyGenerator keyGenerator() {return new KeyGenerator() {@Overridepublic Object generate(Object target, Method method, Object... params) {StringBuilder sb = new StringBuilder();sb.append(target.getClass().getName());sb.append(method.getName());for (Object obj : params) {sb.append(obj.toString());}return sb.toString();}};}/*** 设置RedisTemplate规则** @param redisConnectionFactory* @return*/@Beanpublic RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);//解决查询缓存转换异常的问题ObjectMapper om = new ObjectMapper();// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和publicom.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);//序列号key valueredisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);redisTemplate.afterPropertiesSet();return redisTemplate;}/*** 设置CacheManager缓存规则** @param factory* @return*/@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisSerializer<String> redisSerializer = new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);//解决查询缓存转换异常的问题ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);// 配置序列化(解决乱码的问题),过期时间600秒RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).disableCachingNullValues();RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();return cacheManager;}
}
说明:
@EnableCaching
:标记注解
@EnableCaching
,开启缓存,并配置Redis缓存管理器。@EnableCaching
注释触发后置处理器,检查每一个Spring bean 的 public 方法是否存在缓存注解。如果找到这样的一个注释,自动创建一个代理拦截方法调用和处理相应的缓存行为。
⑵ 使用Spring Cache
1) 常用缓存标签
① 缓存@Cacheable
根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。
查看源码,属性值如下:
属性/方法名 | 解释 |
---|---|
value | 缓存名,必填,它指定了你的缓存存放在哪块命名空间 |
cacheNames | 与 value 差不多,二选一即可 |
key | 可选属性,可以使用 SpEL 标签自定义缓存的key |
② 缓存@CachePut
使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。
查看源码,属性值如下:
属性/方法名 | 解释 |
---|---|
value | 缓存名,必填,它指定了你的缓存存放在哪块命名空间 |
cacheNames | 与 value 差不多,二选一即可 |
key | 可选属性,可以使用 SpEL 标签自定义缓存的key |
③ 缓存@CacheEvict
使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上
查看源码,属性值如下:
属性/方法名 | 解释 |
---|---|
value | 缓存名,必填,它指定了你的缓存存放在哪块命名空间 |
cacheNames | 与 value 差不多,二选一即可 |
key | 可选属性,可以使用 SpEL 标签自定义缓存的key |
allEntries | 是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存 |
beforeInvocation | 是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存 |
2) 数据字典应用Spring Cache + Redis 缓存数据
① 在service-cmn的application.properties添加redis配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
② 改造DictServiceImpl类方法
com.kejizhentan.cmn.service.impl.DictServiceImpl.java
/*** @Auther: kejizhentan* @Date 2022/11/27 16:32* @Description: 根据数据id查询子数据列表*/
@Cacheable(value = "dict",keyGenerator = "keyGenerator")
@Override
public List<Dict> findChlidData(Long id) {QueryWrapper<Dict> wrapper = new QueryWrapper<>();wrapper.eq("parent_id",id);List<Dict> dictList = baseMapper.selectList(wrapper);//向list集合每个dict对象中设置hasChildrenfor (Dict dict:dictList) {Long dictId = dict.getId();boolean isChild = this.isChildren(dictId);dict.setHasChildren(isChild);}return dictList;
}/*** @Auther: kejizhentan* @Date 2022/11/27 13:07* @Description: 导入数据字典, allEntries=true方法调用后清空所有缓存*/@CacheEvict(value = "dict", allEntries=true)@Overridepublic void importDict(MultipartFile file) {try {EasyExcel.read(file.getInputStream(), DictEeVo.class, new DictListener(baseMapper)).sheet().doRead();} catch (IOException e) {throw new RuntimeException(e);}}
效果如下
9、使用nginx统一api接口
由于我们后端有很多服务模块,每个模块都有对应的访问路径与端口,为了提供统一的api接口,所以使用nginx作为反向代理服务器;
反向代理,其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问,我们只需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器IP地址
1,下载安装nginx(window版)
2,配置nginx
server {listen 9001;server_name localhost;location ~ /hosp/ { proxy_pass http://localhost:8201;}location ~ /cmn/ { proxy_pass http://localhost:8202;}
}
3,调整/config/dev.env.js中的BASE_API
BASE_API: 'http://localhost:9001'
说明:
1、后续我们会使用Spring Cloud Gateway网关,将替代nginx网关
效果如下:
预约挂号系统技术点详解(一)相关推荐
- 预约挂号系统技术点详解(二)
一.微服务间服务的调用介绍 1. 需求(医院接口远程调用数据字典) service-hosp服务调用service-cmn服务 2. 实现步骤 ⑴ 搭建service-client父模块 修改pom文 ...
- 基于SSM的医院预约挂号系统 JAVA MYSQL
10187_基于SSM的医院预约挂号系统 技术 SSM 工具 eclipse + tomcat + mysql + jdk 功能详情 前台界面:
- (附源码)node.js-医院预约挂号系统的设计与实现 毕业设计141041
医院预约挂号系统 摘 要 随着科学技术的飞速发展,各行各业都在努力与现代先进技术接轨,通过科技手段提高自身的优势:对于医院预约挂号系统当然也不能排除在外,随着网络技术的不断成熟,带动了医院预约挂号系统 ...
- 【计算机毕业设计】医院预约挂号系统
一.系统截图(需要演示视频可以私聊) 摘 要 随着科学技术的飞速发展,社会的方方面面.各行各业都在努力与现代的先进技术接轨,通过科技手段来提高自身的优势,医院预约挂号系统当然也不能排除在外.医院预约 ...
- [Python从零到壹] 九.网络爬虫之Selenium基础技术万字详解(定位元素、常用方法、键盘鼠标操作)
欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...
- 分布式表格系统Google Bigtable详解
分布式表格系统Google Bigtable详解 概述 Bigtable架构 数据分布 保证 副本位置与负载均衡 存储 表的分裂与合并 存储引擎 垃圾回收 总结 概述 bigtable系统由表格组成, ...
- 计算机三级网络技术知识点cn,计算机等级三级网络技术考试详解
首页 > 办公休闲手游 计算机等级三级网络技术考试详解 一.基本知识 1.具有计算机软件及 应用的基本知识 2.掌握操作系统的基 本知识 3.掌握计算机网络的基本概念与基 本工作原理 4.掌握I ...
- 网上预约挂号系统的设计与实现
项目描述 临近学期结束,还是毕业设计,你还在做java程序网络编程,期末作业,老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等.这里根据疫情当下,你 ...
- 医院在线预约挂号系统 jsp+mysql+maven
医院在线预约挂号系统 jsp+mysql+maven 挂号预约系统 选题背景 现有某医院想要提升患者挂号管理水平,打算引入一套挂号预约管理系统. 功能需求 挂号预约系统主要包括基础数据管理.预约管理. ...
最新文章
- Leangoo敏捷工具如何升级至企业版
- OpenCV人脸识别LBPH算法源码分析
- java短信接口 调用_带你了解短信接口的调用
- 单片机c语言参考文献最新,[2018年最新整理]10个单片机C语言实例.doc
- 人物志 | KDD Cup 2017双料冠军燕鹏
- Flink on Zeppelin 流计算处理最佳实践
- 四十个非常实用的轻量级JavaScript库
- 微信红包封面向个人开放,1元定制!
- 人类再次彻底败给 AI!
- cassandra学习笔记一
- 获得中文每个字的拼音首字母
- C# 多线程处理 I(转载)
- 今日头条的针锋相对让腾讯开始焦虑,天天快报能否占据一席之位?
- Phpstudy简介与使用教程
- Java开发常用的在线工具
- SQL server 实验五 (sql 查询语句)
- 节奏模仿练习——视唱练耳
- Crypto.com、Dock项目评级更新 | TokenInsight
- 华为智能汽车产业研究与投资机会分析
- Spring Security(15)——权限鉴定结构