最新消息: USBMI致力于为网友们分享Windows、安卓、IOS等主流手机系统相关的资讯以及评测、同时提供相关教程、应用、软件下载等服务。

尚医通项目(上)

IT圈 admin 5浏览 0评论

尚医通项目(上)

来自atguigu 视频链接

项目介绍

1.概述

尚医通即为网上预约挂号系统,旨在缓解看病难、挂号难的就医难题。随时随地轻松挂号!不用排长队!

2.技术点

核心技术
SpringBoot:简化新Spring应用的初始搭建以及开发过程
SpringCloud:基于Spring Boot实现的云原生应用开发工具,SpringCloud使用的技术:(SpringCloudGateway、Spring Cloud Alibaba Nacos、Spring Cloud Alibaba Sentinel、SpringCloud Task和SpringCloudFeign等)
MyBatis-Plus:持久层框架
Redis:内存缓存
RabbitMQ:消息中间件
HTTPClient: Http协议客户端
Swagger2:Api接口文档工具
Nginx:负载均衡
Lombok:简化实体类的开发
Mysql:关系型数据库
MongoDB:面向文档的NoSQL数据库
Vue.js:web 界面的渐进式框架
Node.js: JavaScript 运行环境
Axios:Axios 是一个基于 promise 的 HTTP 库
NPM:包管理器
Babel:转码器
Webpack:打包工具
Docker :容器技术
Git:代码管理工具

3.项目流程

4.架构

Mybatis-Plus

详情见另一篇文章,链接;
此项目大概涉及以下内容,可针对性学习。

  1. 主键策略 @TableId★

  2. 字段自动填充 @TableField★

  3. 乐观锁 悲观锁

  4. 基础CRUD

  5. 分页查询 ★

  6. 逻辑删除 ★.

    条件构造器

springboot2.1之后内置mysql8.0驱动。

项目基础搭建

1.项目结构及搭建

可以选择新建springboot项目或maven项目两种方式搭建;父模块打包方式为pom,具体搭建看视频吧。

yyds-parent:根目录,管理子模块common:公共模块父节点common-util:工具类模块,所有模块都可以依赖于它rabbit-util:rabbitmq业务封装service-util:service服务的工具包,包含service服务的公共配置类,所有service模块依赖于它hospital-manage:医院接口模拟端model:实体类模块server-gateway:服务网关service:api接口服务父节点service-cmn:字典api接口服务service-hosp:医院api接口服务service-order:订单api接口服务service-oss:文件存储api接口服务service-sms:短信api接口服务service-statistics:统计api接口服务service-task:定时任务服务service-user:用户api接口服务service-client:feign服务调用父节点service-cmn-client:字典api接口service-hosp-client:医院api接口service-order-client:订单api接口service-user-client:用户api接口
搭建模块
yygh_parentcommoncommon_utilservice_utilmodelserviceservice_hospservice_user

2.集成Swagger2

一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务

常用注解

举例:
@EnableSwagger2    声明支持Swagger。声明在配置类“@Configuration”@Api(description = "医院设置接口")   定义controller层接口说明@ApiOperation(value = "医院设置列表")   定义方法说明@ApiModelProperty(value = "姓名")   定义实体属性说明@ApiParam(name = "id", value = "讲师ID", required = true)   定义参数说明

依赖及搭建步骤

因为swagger测试只有service模块里的服务用得到

在common模块service_util的config包中新建配置类Swagger2Config

@Configuration
@EnableSwagger2
public class Swagger2Config {@Beanpublic Docket webApiConfig(){return new Docket(DocumentationType.SWAGGER_2).groupName("webApi").apiInfo(webApiInfo()).select()//只显示api路径下的页面,包含/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("xiaoxin", "=blog", "839623440@qq.com")).build();}private ApiInfo adminApiInfo(){return new ApiInfoBuilder().title("后台管理系统-API文档").description("本文档描述了后台管理系统微服务接口定义").version("1.0").contact(new Contact("xiaoxin", "=blog", "839623440@qq.com")).build();}
}

依赖
common下的pom.xml

        <!--swagger2--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId></dependency>

service 项目添加依赖

<dependency><groupId>com.atguigu</groupId><artifactId>service_utils</artifactId><version>0.0.1-SNAPSHOT</version>
</dependency>

测试界面访问

需要在启动类上添加@ComponentScan(basePackages = “com.atguigu”)
否则扫描不到配置类

http://localhost:8201/swagger-ui.html

3.统一结果返回

common模块下创建子模块common_utils
1.状态码定义

2.统一结果类

@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;}
//结果数据和状态码、信息的添加,其中resultCodeEnum可以是如ResultCodeEnum.SUCCESS/ResultCodeEnum.FAILpublic 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);}//可以链式调用(返回的Result对象),可重新set返回信息,不在单纯是fail的失败public Result<T> message(String msg){this.setMessage(msg);return this;}

4.统一异常处理

@ControllerAdvice spring AOP面向切面编程,对Controller进行切面环绕。
作用:全局异常处理、全局数据预处理、全局数据绑定
@ExceptionHandler (Exception.class) 异常拦截器(自定义异常处理器),需要结合@ControllerAdvice一起使用

common/common_util/exception包

系统异常

@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)@ResponseBodypublic Result error(Exception e){//e.printStackTrace();return Result.fail(e.getmessage());}@ExceptionHandler(YyghException.class)@ResponseBodypublic Result error(YyghException e){e.printStackTrace();return Result.fail();}
}

特殊异常

自定义异常

有些系统异常不能满足我们的需求
返回结果和自定义异常分开,这里自定义异常new了之后toString打印在了控制台;感觉这部分不是很好,后头借鉴一下谷粒学苑的吧

@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() +'}';}
}

思考
对于枚举类状态码信息的操作;感觉在下面这种情况
if (resultMap == null) {//出错
return Result.fail().message(“支付出错”);
}
可以throw,new自定义异常类,返回错误信息(需要在enum中在新建一个对象)
也可以在返回结果类时重新set错误信息;

5.统一日志处理(了解)

spring boot内部使用Logback作为日志实现的框架。

配置日志级别日志记录器(Logger)的行为是分等级的。如下表所示:分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL默认情况下,spring boot从控制台打印出来的日志级别只有INFO及以上级别,可以配置日志级别# 设置日志级别
logging.level.root=WARN
这种方式只能将日志打印在控制台上sout 会产生io,需要删掉

日志配置

<?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:/JavaStudy/graduationDesign/yygh_log" /><!-- 彩色日志 --><!-- 配置格式变量: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>

医院设置做什么:不同医院和预约挂号平台建立链接;每个医院有自己的编号。

医院设置主要是用来保存开通医院的一些基本信息,每个医院一条信息,保存了医院编号(平台分配,全局唯一)和接口调用相关的签名key等信息,是整个流程的第一步,只有开通了医院设置信息,才可以上传医院相关信息。
医院被锁定后便不能再上传信息。

医院设置接口

数据模型


提示:使用mp,在service层调用basemapper不用再注入,因为继承的ServiceImpl已经注入了

service_hosp模块

1.带条件带分页查询接口

添加分页插件

@Configuration
@MapperScan("com.atguigu.yygh.hosp.mapper")
public class HospConfig {/*** 分页插件**/@Beanpublic PaginationInterceptor paginationInterceptor(){return new PaginationInterceptor();}
}
**HospitalSetController层接口**
@Api(tags = "医院设置管理")
@RestController
@RequestMapping("/admin/hosp/hospitalSet")
public class HospitalSetController 

vo对象

HospitalSetQueryVo
包含医院名称和编号
	/*** 条件查询带分页* 注意:@PostMapping 才能获取@RequestBody传来的json数据* 		@responseBody底层是使用Jackson来完成对象到json的转换*      @RequestBody(required = false) 代表这个数据可以不传* @param current:当前页* @param limit:每页显示几条数据* @param hospitalSetQueryVo:查询条件(医院编号、医院名称(模糊查询))* @return*/@ApiOperation(value = "条件查询带分页")@PostMapping("findPageHospSet/{current}/{limit}")public Result findPageHospSet(@PathVariable("current") long current,@PathVariable("limit") long limit,@RequestBody(required = false) HospitalSetQueryVo hospitalSetQueryVo){//创建page对象,传递当前页,每页记录数Page<HospitalSet> page = new Page<>(current,limit);QueryWrapper<HospitalSet> wrapper = new QueryWrapper<>();if (!StringUtils.isEmpty(hospitalSetQueryVo)) {String hosname = hospitalSetQueryVo.getHosname();String hoscode = hospitalSetQueryVo.getHoscode();//构造条件wrapper.like(!StringUtils.isEmpty(hosname),"hosname",hosname).eq(!StringUtils.isEmpty(hoscode),"hoscode", hoscode);//调用方法实现分页的查询Page<HospitalSet> hospitalSetPage = hospitalSetService.page(page, wrapper);//返回结果return Result.ok(hospitalSetPage);}

page对象常用属性;(mybatis-plus封装的)

2.新增接口

添加MD5工具类

controller层接口

    @ApiOperation(value = "添加医院设置")@PostMapping("/saveHospitalSet")public Result saveHospitalSet(@RequestBody HospitalSet hospitalSet) {// 设置状态 1使用 0不能使用hospitalSet.setStatus(1);// 设置签名密钥Random random = new Random();hospitalSet.setSignKey(MD5.encrypt(System.currentTimeMillis() + "" + random.nextInt(1000)));boolean save = hospitalSetService.save(hospitalSet);return save?Result.ok():Result.fail();}

数据库中设置了时间、状态的默认值,所以不用添加;但是还是可以做个mp的自动填充,修改时时间会自动改变等等。

3.修改接口

说明:此接口同时需要实现根据id查询,以实现修改时的数据回显。

	/***根据id获取医院设置* @param id 医院id* @return 医院设置信息*/@GetMapping("getHospitalSetById/{id}")public Result getHospitalSetById(@PathVariable("id") Long  id){HospitalSet hospitalSet = hospitalSetService.getById(id);return Result.ok(hospitalSet);}/***修改医院设置* @param hospitalSet 修改后医院设置信息* @return 修改的结果*/@PostMapping("updateHospitalSet")public Result updateHospitalSet(@RequestBody HospitalSet hospitalSet){boolean flag = hospitalSetService.updateById(hospitalSet);return flag?Result.ok():Result.fail();}

4.批量删除接口

删除

    /*** 逻辑删除医院设置* @return*/@ApiOperation(value = "逻辑删除医院设置")@DeleteMapping("/{id}")public Result removeHospSet(@PathVariable("id") long id) {boolean flag = hospitalSetService.removeById(id);if (flag) {return Result.ok();} else {return Result.fail();}}

批量删除

	/*** 批量删除医院设置* @param idList 要删除的id集合* @return 返回批量删除的结果*/@DeleteMapping("batchRemove")public Result batchRemoveHospitalSet(@RequestBody List<Long> idList){boolean flag = hospitalSetService.removeByIds(idList);return flag?Result.ok():Result.fail();}

弹幕说集合没有实现序列化,分布式环境会接受不了数据?

5.锁定医院设置

	/*** 医院设置锁定和解锁的* @param id 医院id* @param status 该医院的状态* @return*/@PutMapping("lockHospitalSet/{id}/{status}")public Result lockHospitalSet(@PathVariable Long id,@PathVariable Integer status){//先根据id查询医院设置信息HospitalSet hospitalSet = hospitalSetService.getById(id);//设置状态hospitalSet.setStatus(status);boolean flag = hospitalSetService.updateById(hospitalSet);return flag?Result.ok():Result.fail();}

6.发送签名密钥

烂尾了。

    @ApiOperation(value = "发送签名秘钥")@PutMapping("/sendKey/{id}")public Result sendKey(@PathVariable Long id) {HospitalSet hospitalSet = hospitalSetService.getById(id);String signKey = hospitalSet.getSignKey();String hoscode = hospitalSet.getHoscode();//TODO 发送短信return Result.ok();}
}

后台前端

vue-element-admin GitHub地址
vue-admin-template GitHub地址
项目在线预览:地址
elementui(基于vue2x) 地址
建议:你可以在 vue-admin-template 的基础上进行二次开发,把 vue-element-admin当做工具箱,想要什么功能或者组件就去 vue-element-admin 那里复制过来

//克隆工程  有关git可以看[这篇文章]()
git clone .git
//下载相关依赖
npm install
//运行npm run dev

如果下载以依赖出现node-sass类错,尝试先执行下面的代码

npm i -g node-sass --sass_binary_site=/

一.结构

vue-damin-temeplatebulid:构建相关config:全局配置src:源代码api:所有请求assets:主题 字体等静态资源components:全局公共组件icons:项目所有svg iconsrouter:路由store:全局store管理styles:全局样式utils:全局公用方法views:视图	App.vue:入口页面main.js:入口 加载组件 初始化等permission.js:权限管理static:静态资源.babelrc:babel-loader配置.eslintrc.js:eslint配置项.gitignore:git忽略项package.json:依赖管理

二.项目中的重要文件

package.js
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.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

注意

修改配置文件中的语法检查,如果不能接受一直报错;改为false

实现前面所写接口的前端页面

三.登录简易改造

前端

把登陆写死的,不用再请求。

  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':'.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 }) {// return new Promise(resolve => {commit('SET_TOKEN', '')removeToken()resolve()// })}}

前台开发步骤


修改请求路径
VUE_APP_BASE_API = 'http://localhost:8201/
修改api接口方法 请求路径

跨域问题解决方案

在controller层中添加注解@CrossOrigin

四.医院设置列表

1.添加路由,创建页面文件

  {path: '/yygh/hospset',component: Layout,redirect: '/yygh/hospset/list',name: 'hosp',meta: { title: '医院管理', icon: 'el-icon-s-help' },children: [{path: 'list',name: '医院设置列表',component: () => import('@/views/yygh/hospset/list'),meta: { title: '医院设置列表', icon: 'table' }},{path: 'add',name: '医院设置表单',component: () => import('@/views/yygh/hospset/add'),meta: { title: '添加医院设置', icon: 'tree' }}]},

2.创建API

src\api\hosp.js

import request from '@/utils/request'
//提取请求路径
const api_name = '/admin/hosp/hospitalSet'export default{//分页条件查询医院设置pageQuery(page,limit,searchObj){return request({url: `${api_name}/pageQuery/${page}/${limit}`,//插值表达式method: 'post',data:searchObj  //使用json方式传递数据;如果写param,则前面不用加data})}
}

3.编写页面

添加分页元素

slot-scope代表整个表格;.代表每一行
prop属性

<template><div class="app-container">医院设置列表<el-tablev-loading="listLoading":data="list"element-loading-text="数据加载中"borderfithighlight-current-row><el-table-column label="序号" width="70" align="center"><template slot-scope="scope">{{ (page - 1) * limit + scope.$index + 1 }}</template></el-table-column><!-- 字段名要与对象属性一致 --><el-table-column prop="hosname" label="医院名称" width="180" /><el-table-column prop="hoscode" label="医院编号" width="160" /><el-table-column prop="apiUrl" label="地址" width="200" /><el-table-column prop="contactsName" label="联系人" /><el-table-column prop="status" label="状态"><template slot-scope="scope"> {{ scope.row.status === 1 ? "可用" : "不可用" }} <!-- === 比较类型也比较值 --></template></el-table-column><el-table-column label="操作" width="200" align="center"><template slot-scope="scope"><router-link :to="'/yygh/hospset/edit/' + scope.row.id"><el-button type="primary" size="mini" icon="el-icon-edit">修改</el-button></router-link><el-buttontype="danger"size="mini"icon="el-icon-delete"@click="removeDataById(scope.row.id)">删除</el-button></template></el-table-column></el-table></div>
</template>

底部分页组件

    <!-- 分页 --><el-pagination:current-page="page":page-size="limit":total="total"style="padding: 30px 0; text-align: center;"layout="total, prev, pager, next, jumper"//会自动传当前页,封装好的@current-change="fetchData"/>

分页查询表单

           医院设置列表<!--查询表单inline一行显示--><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="fetchData()">查询</el-button><el-button type="default" @click="resetData()">清空</el-button></el-form>

页面逻辑js

//引入接口
imoort pageQuery  from '@/api/hospset'
//------------------
data() {return {listLoading: true, //表格加载状态list: [], //表格数据page: 1, //当前页limit: 10, //每页记录数total:0,//总记录数searchObj: {} //查询条件};},created() {this.fetchData();},methods: {//分页条件查询 添加默认传参 es6新特性;fetchData(page=1) {this.page = page//hospsetApi为引入的那个接口名,里面包含了那个文件里的所有接口方法hospsetApi.pageQuery(this.page, this.limit, this.searchObj).then(response => {console.log(response);this.list = response.data.pageModel.records;this.total = response.data.pageModel.totalthis.listLoading = false;});},//清空resetData() {this.searchObj = {};this.fetchData();},}

五.删除医院设置

1. 添加api接口

    //医院设置删除removeById(id){return request({url: `${api_name}/${id}`,//插值表达式method: 'delete'})},

2.页面js

删除和修改按钮元素,在前面已经写了

 <el-table-column label="操作" width="200" align="center"><template slot-scope="scope"><router-link :to="'/yygh/hospset/edit/' + scope.row.id"><el-button type="primary" size="mini" icon="el-icon-edit">修改</el-button></router-link><el-buttontype="danger"size="mini"icon="el-icon-delete"<!--这里会传入要删除的id-->@click="removeDataById(scope.row.id)">删除</el-button></template></el-table-column>
        //删除removeDataById(id) {this.$confirm("此操作将永久删除该数据, 是否继续?", "提示", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",//确定执行then的,取消执行catch的}).then(() => {//删除hospsetApi.removeById(id).then((response) => {//刷新页面;也可以用window.location.reload();this.fetchData();//try传参current});this.$message({type: "success",message: "删除成功!",});}).catch(() => {this.$message({type: "info",message: "已取消删除",});});},

六.新增医院设置

1. 实现接口API

    //新增医院设置save(hospitalSet){return request({url: `${api_name}/save`,//插值表达式method: 'post',data:hospitalSet})},

2.编写页面(add.vue)

新增与修改共用一个页面

表单元素

<template><div class="app-container">医院设置表单<el-form label-width="120px"><el-form-item label="医院名称"><el-input v-model="hospset.hosname"/></el-form-item><el-form-item label="医院编号"><el-input v-model="hospset.hoscode"/></el-form-item><el-form-item label="api地址"><el-input v-model="hospset.apiUrl"/></el-form-item><el-form-item label="联系人"><el-input v-model="hospset.contactsName"/></el-form-item><el-form-item label="电话"><el-input v-model="hospset.contactsPhone"/></el-form-item><el-form-item><el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保存</el-button></el-form-item></el-form></div>
</template>

页面js

页面跳转用 r o u t e r ,获取元素用 router,获取元素用 router,获取元素用route;注意区别

<script>
import hospsetApi from "@/api/yygh/hospset";
export default {data() {return {hospset: {}, //表单对象saveBtnDisabled: false //按钮是否不可操作};},created() {},methods: {//保存saveOrUpdate() {//新增this.saveHospset();},//新增saveHospset() {hospsetApi.save(this.hospset).then(response => {this.$message({type: "success",message: "新增成功!"});//路由页面跳转this.$router.push({path:'/yygh/hospset/list'})});}}
};
</script>

七.医院设置修改

修改元素在前面已经写好

1.隐藏路由

什么是隐藏路由

      {//占位符设置什么名字,取值时就用什么path: 'edit/:id',name: 'HospSetEdit',component: () => import('@/views/yygh/hospset/add'),meta: { title: '编辑医院设置', noCache: 'tree' },hidden: true}

2.修改时的数据回显

1.添加api
    //根据id进行查询getById(id){return request({url: `${api_name}/getById/${id}`,//插值表达式method: 'get'})},
2.页面js
          <router-link :to="'/yygh/hospset/edit/' + scope.row.id"><el-button type="primary" size="mini" icon="el-icon-edit">修改</el-button></router-link>created () {//通过路由获取id(占位符),判断id数据是否存在(有参数且有id)if(this.$route.params&&this.$route.params.id){//注意,获取参数用route,不是router//console.log(this.$route.params.id);//数据回显hospsetApi.getById(this.$route.params.id).then(response=>{this.hospset = response.data.hospitalSet})}},

3.实现修改保存

1.新增api
    //修改医院设置update(hospitalSet){return request({url: `${api_name}/update`,method: 'post',data: hospitalSet  //用json})},
2.页面js
        methods: {//保存saveOrUpdate(){//新增与修改的区别:vu对象是否存在idthis.saveBtnDisabled = trueif (!this.hospset.id) {//新增this.saveHospset()} else {//编辑this.updateHospset()}},//修改updateHospset(){hospsetApi.update(this.hospset).then(response=>{this.$message({type: "success",message: "修改成功!",});//页面跳转,用routerthis.$router.push({path:'/yygh/hospset/list'})});},

思考:也可以不写两个方法(添加,修改);直接都传一个对象给后端,用mp的saveOrUpdate方法或者自己写一个。

八.医院设置批量删除

实现步骤
给表格添加复选框,可以拿到选择的对象
点击“批量删除”按钮,获取所有选择对象的id,存入集合

1.添加API

    //批量删除医院设置batchRemove(idList){return request({url: `${api_name}/batchRemove`,//插值表达式method: 'delete',data: idList})},

2.编写页面

只是添加了批量删除复选框

        <!-- 工具条 --><div><el-button type="danger" size="mini" @click="removeRows()">批量删除</el-button></div><!-- alt + shift + f 格式化页面 --><!-- 复选框 批量修改 @selection-change="handleSelectionChange" --><el-tablev-loading="listLoading":data="list"element-loading-text="数据加载中"borderfithighlight-current-row@selection-change="handleSelectionChange">   <!--selection表示复选框--><el-table-column type="selection" width="55" />

3.页面js

复选框选择的method实现

    data() {return {listLoading: true, //表格加载状态list: [], //表格数据page: 1, //当前页limit: 5, //每页记录数total: 0,searchObj: {}, //查询条件multipleSelection: [], // 批量选择中选择的记录列表(批量删除)};},...methods: {....//复选框选择方法;每次选择复选框会触发,传入对象(每行一个对象)handleSelectionChange(selection) {console.log(selection);this.multipleSelection = selection;},

批量删除method实现
记得判断一下如果没选择,提醒用户无法删除

//批量删除removeRows() {this.$confirm("此操作将永久删除该数据, 是否继续?", "提示", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",}).then(() => {//1.创建参数对象idListlet idList = [];//2.遍历multipleSelection对象,取出idfor (let i = 0; i < this.multipleSelection.length; i++) {let hospset = this.multipleSelection[i];let id = hospset.id;//3.存入idListidList.push(id);}console.log(idList);//4.调接口进行批量删除hospsetApi.batchRemove(idList).then((response) => {this.fetchData();this.$message({type: "success",message: "删除成功!",});});}).catch(() => {this.$message({type: "info",message: "已取消删除",});});},

九.锁定和取消锁定

1.新增api

    //锁定和取消锁定lockHospSet(id, status) {return request({url: `${api_name}/lockHospitalSet/${id}/${status}`,method: 'put'})},

2.页面js

之前的页面元素(删除、保存、修改、锁定)

            <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><el-button v-if="scope.row.status==1" type="danger" size="mini"    icon="el-icon-delete" @click="lockHospSet(scope.row.id,0)">锁定</el-button><el-button v-if="scope.row.status==0" type="primary" size="mini"    icon="el-icon-delete" @click="lockHospSet(scope.row.id,1)">取消锁定</el-button><router-link :to="'/hospSet/edit/'+scope.row.id"><el-button type="primary" size="mini" icon="el-icon-edit"></el-button></router-link></template></el-table-column>
//锁定和取消锁定lockHospSet(id,status){hospset.lockHospSet(id,status).then(response=>{//刷新页面this.getList()})},

数据字典

数据字典就是管理系统常用的分类数据或者一些固定数据,例如:省市区三级联动数据、民族数据、行业数据、学历数据等,由于该系统大量使用这种数据,所以我们要做一个数据管理方便管理系统数据,一般系统基本都会做数据管理。
数据字典就是系统中数据展示文字和编码的映射关系

一.表结构及分析

二.service_cmn模块搭建

三.数据字典列表

根据element组件要求,返回列表数据必须包含hasChildren字典;即是否包含子节点

在model模块查看Dict类

    @ApiModelProperty(value = "是否包含子节点")//表示在数据库中并不存在,不写下面的注解会报错的@TableField(exist = false)private boolean hasChildren;

后端实现

1.添加config配置类
@Configuration
//开启事务管理?
@EnableTransactionManagement
@MapperScan("com.atguigu.yygh.cmn.mapper")
public class CmnConfig {
}
2.实现接口

DictController

@Api(description = "数据字典接口")
@RestController
@RequestMapping("/admin/cmn/dict")
@CrossOrigin
public class DictController {@Autowiredprivate DictService dictService;//根据数据id查询子数据列表@ApiOperation(value = "根据数据id查询子数据列表")@GetMapping("findChildData/{id}")public R findChildData(@PathVariable Long id){List<Dict> list = dictService.findChildData(id);return R.ok().data("list",list);}}

DictServiceImpl
查询:select * from dict where parent_id = id;
下面代码可以考虑用stream优化(for循环里有数据库操作)


@Service
public class DictServiceImplextends ServiceImpl<DictMapper, Dict>implements DictService {//根据数据id查询子数据列表@Overridepublic List<Dict> findChildData(Long id) {//1.根据父id查询子级别数据集合QueryWrapper<Dict> wrapper = new QueryWrapper<>();wrapper.eq("parent_id",id);List<Dict> dictList = baseMapper.selectList(wrapper);//2.遍历查询是否有子数据for (Dict dict : dictList) {boolean hasChildren = this.isChildren(dict.getId());dict.setHasChildren(hasChildren);}return dictList;}//查询是否有子数据,方法提取private boolean isChildren(Long id) {QueryWrapper<Dict> wrapper = new QueryWrapper<>();wrapper.eq("parent_id",id);Integer count = baseMapper.selectCount(wrapper);return count > 0;}
}
测试结果

前端实现

1.创建路由和页面文件

alwaysShow: true, //总是展示一级菜单
不加这个属性。当只有一个children,便只显示childen了

  {path: '/cmn',component: Layout,redirect: '/cmn/list',name: '数据管理',alwaysShow: true, //总是展示一级菜单meta: { title: '数据管理', icon: 'example' },children: [{path: 'list',name: '数据字典',component: () => import('@/views/yygh/dict/list'),meta: { title: '数据字典', icon: 'table' }}]},

2.创建API接口

api/dict.js

import request from '@/utils/request'
//提取请求路径
const api_name = '/admin/cmn/dict'export default{//根据数据id查询子数据列表findChildData(id){return request({url: `${api_name}/findChildData/${id}`,//插值表达式method: 'get'})},}
3.添加页面元素

注意:若页面显示不对,可能是element-ui版本过低,修改版本后,删除node-moudle里的,再重新下载。
lazy属性为懒加载,只加载一级节点 ,通过 :load=“load” 方法加载子节点数据

<template><div class="app-container"> <!-- lazy 只加载一级节点 :load="load"  --><el-table :data="list" style="width: 100%" row-key="id" border lazy <!--load方法,封装的,会递归查询。-->:load="load"<!--hasChildren用处在这-->:tree-props="{children: 'children', hasChildren: 'hasChildren'}"><el-table-column prop="name" label="名称" width="150"></el-table-column><el-table-column prop="dictCode" label="编码" width="150"></el-table-column><el-table-column prop="value" label="值" width="150"></el-table-column><el-table-column prop="createTime" label="创建时间"></el-table-column></el-table></div>
</template>
4.页面js
<script>
import dictApi from "@/api/yygh/dict"
export default{data() {return {list: []  //字典数据集合   }},created() {//全部分类id为1this.getDate(1)},methods: {//初始化查询方法getDate(id){dictApi.findChildData(id).then(response=>{this.list = response.data.list})},//树形节点加载方法,tree为节点对象,通过resolve()加载数据load(tree, treeNode, resolve){//传入结点id,继续查询子节点dictApi.findChildData(tree.id).then(response=>{//参考官方文档resolve(response.data.list)})}}
}</script>

此时后端已经写了两个模块,对应不同端口,发请求的请求地址已经出现问题
解决方案:使用nginx作请求转发;统一设置请求路径为nginx的路径(VUE_APP_BASE_API = ‘http://localhost:9001’)

Nginx

1.nginx的下载、安装、启动
2.修改配置文件

所在位置为nginx/conf;一定要放在http块里

	server {#监听端口listen       9001;#nginx所在主机域名server_name  localhost;location ~ /hosp/ {      #转发     proxy_pass http://localhost:8201;}location ~ /cmn/ {     #转发      proxy_pass http://localhost:8202;}
}

四.EasyExcel操作

1.概述

Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。面对高访问高并发,一定会OOM或者JVM频繁的full gc (重jc)。

EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
github地址

文档地址

2.测试demo

写操作

导入依赖

<!--EasyExcel-->
<dependencies><!-- .alibaba/easyexcel --><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.1.1</version></dependency>
</dependencies>

实体对象

@Data
public class Stu {//设置表头名称@ExcelProperty("学生编号")private int sno;//设置表头名称@ExcelProperty("学生姓名")private String sname;}

测试类

public class WriteTest {public static void main(String[] args) {String fileName = "E:\\test.xlsx";EasyExcel.write(fileName,Stu.class)//文件名/流,class类型.sheet("学生信息")//设置sheet名.doWrite(data());//传入要写的list集合data}//循环设置要添加的数据,最终封装到list集合中private static List<Stu> data() {List<Stu> list = new ArrayList<Stu>();for (int i = 0; i < 10; i++) {Stu data = new Stu();data.setSno(i);data.setSname("lucy"+i);list.add(data);}return list;}
}
读操作

改造实体类,指定对应关系

@Data
public class Stu {//设置表头名称,指定映射关系(第0列);index即为索引@ExcelProperty(value = "学生编号",index = 0)private int sno;//设置表头名称,指定映射关系(第1列)@ExcelProperty(value = "学生姓名",index = 1)private String sname;}

创建监听器

public class ExcelListener extends AnalysisEventListener<Stu> {//一行一行去读取excle内容,从第二行开始@Overridepublic void invoke(Stu stu, AnalysisContext analysisContext) {System.out.println("stu = " + stu);}//读取excel表头信息@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {System.out.println("表头信息:"+headMap);}//读取完成后执行@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {}
}

测试

public class ReadTest {public static void main(String[] args) {String fileName = "E:\\test.xlsx";//文件名,class类型,监听器EasyExcel.read(fileName,Stu.class,new ExcelListener()).sheet().doRead();}}

总结 :读操作需要配置监听器

五.数据字典导出、导入

1.导出

后端实现

添加依赖

在service_cmn里

<!--EasyExcel-->
<dependencies><!-- .alibaba/easyexcel --><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.1.1</version></dependency>
</dependencies>

返回前端显示对象DictEeVo

@Data
public class DictEeVo {//对应excel表格的行开头信息@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;}

DictController

	/*** 导出数据字典接口* @param response 响应输出流,将Excel返回;方便下载操作* @return 导出结果*/@GetMapping("exportData")public void exportDict(HttpServletResponse response){dictService.exportDictData(response);}

DictServiceImpl

	//导出数据字典接口@Overridepublic void exportDictData(HttpServletResponse response) {//设置下载信息response.setContentType("application/vnd.ms-excel");//文件类型response.setCharacterEncoding("utf-8");//编码//URLEncoder.encode(“哈哈”,“utf-8”);若是英文名,要这样指定编码格式String fileName = "DataDict-"+ UUID.randomUUID();//头信息意思:以下载方式打开response.setHeader("Content-disposition", "attachment;filename="+ fileName + ".xlsx");//查询数据库List<Dict> dictList = baseMapper.selectList(null);//Dict-->DictVoList<DictEeVo> dictEeVoList = new ArrayList<>();for (Dict dict : dictList) {DictEeVo dictEeVo = new DictEeVo();BeanUtils.copyProperties(dict,dictEeVo);dictEeVoList.add(dictEeVo);}//调用方法进行写操作try {EasyExcel.write(response.getOutputStream(), DictEeVo.class).sheet("数据字典").doWrite(dictEeVoList);} catch (IOException e) {throw new YyghException("导出异常",500);}}

记得重温下javaweb写文件…

前端实现

添加页面元素

<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>

页面js

因为不用返回数据,只是发请求,没必要再写在api接口那了。

            //导出数据exportData(){//window.open("http://localhost:8202/admin/cmn/dict/exportData")//照葫芦画瓢。从配置文件读取window.open(`${process.env.VUE_APP_BASE_API}admin/cmn/dict/exportData`)
//window.location.href("http://localhost:8202/admin/cmn/dict/exportData")也可;还可以直接在页面写个a标签,直接在新的页面打开}

2.导入

后端实现

controller层

    @ApiOperation(value = "导入字典数据")@PostMapping("importData")public R importData(MultipartFile file){dictService.importData(file);return R.ok();}

实现监听器

另一种注入dictMapper方式。

public class DictListener extends AnalysisEventListener<DictEeVo> {
private DictMapper dictMapper;public DictListener(DictMapper dictMapper) {this.dictMapper = dictMapper;
}//一行一行读取
@Override
public void invoke(DictEeVo dictEeVo, AnalysisContext analysisContext) {//调用方法添加数据库Dict dict = new Dict();BeanUtils.copyProperties(dictEeVo,dict);dictMapper.insert(dict);
}@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {}

}`

@Component //
public class DictListener extends AnalysisEventListener<DictEeVo> {@Autowiredprivate DictMapper dictMapper;@Overridepublic void invoke(DictEeVo dictEeVo, AnalysisContext analysisContext) {//转换类型Dict dict = new Dict();BeanUtils.copyProperties(dictEeVo,dict);//设置默认值dict.setIsDeleted(0);dictMapper.insert(dict);}@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {}
}

service层

    @AutowiredDictListener dictListener;//导入字典数据@Overridepublic void importData(MultipartFile file) {try {InputStream inputStream = file.getInputStream();EasyExcel.read(inputStream,DictEeVo.class,dictListener).sheet().doRead();} catch (IOException e) {e.printStackTrace();throw new YyghException(20001,"导入数据失败");}}

实体类修改

@Data
@ApiModel(description = "数据字典")
@TableName("dict")
public class Dict {@ApiModelProperty(value = "id")private Long id;@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 = "创建时间")@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 boolean hasChildren;@ApiModelProperty(value = "其他参数")@TableField(exist = false)private Map<String,Object> param = new HashMap<>();}

给的建表sql文件is_deleted默认值为1;记得修改为0
思考,向数据库导入excel中的数据,如果不是覆盖,会越加越多,如何解决?若是可以覆盖,又会被随便修改数据。。

前端实现

添加页面元素

        <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><el-button type="text" @click="importData"><i class="fa fa-plus" />导入</el-button></div></div>

由于无法使用ajax传递文件,所以使用element的组件

上传组件及对话框dialog

        <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="BASE_API" class="upload-demo"><el-button size="small" type="primary">点击上传</el-button><div slot="tip" class="el-upload__tip">只能上传xls文件,且不超过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>

页面js

        data() {return {list: [],  //字典数据集合 dialogImportVisible: false,  //对话框,默认不显示;true时,弹出BASE_API: `${process.env.VUE_APP_BASE_API}admin/cmn/dict/importData` //请求地址}},created() {this.getDate(1)},methods: {....//打开导入对话框importData(){this.dialogImportVisible = true},//上传成功后的操作onUploadSuccess(){//1.关闭对话框this.dialogImportVisible = false//2.上传成功提示this.$message({type: "success",message: "上传成功!"})//3.刷新表格this.getDate(1)}}

六.数据字典添加缓存

添加缓存时机:查询量大,且不经常改变,可以添加redis缓存,提高查询效率。

redis详细文章可以看这个

Spring Cache 是一个非常优秀的缓存组件。自Spring 3.1起,提供了类似于@Transactional注解事务的注解Cache支持,且提供了Cache抽象,方便切换各种底层Cache(如:redis)
使用Spring Cache的好处:
1,提供基本的Cache抽象,方便切换各种底层Cache;
2,通过注解Cache可以实现类似于事务一样,缓存逻辑透明的应用到我们的业务代码上,且只需要更少的代码就可以完成;
3,提供事务回滚时也自动回滚缓存;
4,支持比较复杂的缓存逻辑;
因为缓存也是公共使用,所有的service模块都有可能使用缓存,所以我们把依赖与部分配置加在service-util模块,这样其他service模块都可以使用了

1.整合redis

添加依赖在common_utils模块

    <dependencies><!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- spring2.X集成redis所需common-pool2--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.6.0</version></dependency></dependencies>

添加redis配置类

使用redisTemplate,会出现序列化转换 观察不便的问题;其实也可以用StringRedisTemplate

@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 方法是否存在缓存注解。如果找到这样的一个注释, 自动创建一个代理拦截方法调用和处理相应的缓存行为。

配置文件

#redis
spring.redis.host=
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

2.同步缓存

写操作时,清空相关缓存。再次查询时同步

常用缓存标签

缓存@Cacheable
根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。
属性值如下:
value: 缓存名,必填,它指定了你的缓存存放在哪块命名空间
cacheNames: 与 value 差不多,二选一即可
key: 可选属性,可以使用 SpEL 标签自定义缓存的key

缓存@CachePut 使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。
属性值同上

缓存@CacheEvict 使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法
allEntries 是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存
beforeInvocation 是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存

添加缓存

/*** 根据数据id查询子数据列表* @param id* @return* keyGenerator 生成key的规则,在redisConfig写好过的*/@Cacheable(value = "dict", keyGenerator = "keyGenerator")@Overridepublic List<Dict> findChildData(Long id) 
/*** 数据字典导入* @param file*/@CacheEvict(value = "dict", allEntries = true)@Overridepublic void importData(MultipartFile file)

mongodb

详细见这篇文章吧 链接

医院模拟系统接口开发

一.部署医院模拟系统

主要完成医院、科室、排版接口

父工程下创建 hospital-manage 模块

从资料里复制过来

二.上传(保存)医院信息接口

添加工具类

MD5

public final class MD5 {public static String encrypt(String strSrc) {try {char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8','9', 'a', 'b', 'c', 'd', 'e', 'f' };byte[] bytes = strSrc.getBytes();MessageDigest md = MessageDigest.getInstance("MD5");md.update(bytes);bytes = md.digest();int j = bytes.length;char[] chars = new char[j * 2];int k = 0;for (int i = 0; i < bytes.length; i++) {byte b = bytes[i];chars[k++] = hexChars[b >>> 4 & 0xf];chars[k++] = hexChars[b & 0xf];}return new String(chars);} catch (NoSuchAlgorithmException e) {e.printStackTrace();throw new RuntimeException("MD5加密出错!!+" + e);}}}

HttpRequestHelper

用于发送http请求给hosp模块

@Slf4j
public class HttpRequestHelper {public static void main(String[] args) {Map<String, Object> paramMap = new HashMap<>();paramMap.put("d", "4");paramMap.put("b", "2");paramMap.put("c", "3");paramMap.put("a", "1");paramMap.put("timestamp", getTimestamp());log.info(getSign(paramMap, "111111111"));}/**** @param paramMap* @return*/public static Map<String, Object> switchMap(Map<String, String[]> paramMap) {Map<String, Object> resultMap = new HashMap<>();for (Map.Entry<String, String[]> param : paramMap.entrySet()) {resultMap.put(param.getKey(), param.getValue()[0]);}return resultMap;}/*** 请求数据获取签名* @param paramMap* @param signKey* @return*/public static String getSign(Map<String, Object> paramMap, String signKey) {if(paramMap.containsKey("sign")) {paramMap.remove("sign");}String md5Str = MD5.encrypt(signKey);
//        TreeMap<String, Object> sorted = new TreeMap<>(paramMap);
//        StringBuilder str = new StringBuilder();
//        for (Map.Entry<String, Object> param : sorted.entrySet()) {
//            str.append(param.getValue()).append("|");
//        }
//        str.append(signKey);
//        log.info("加密前:" + str.toString());
//        String md5Str = MD5.encrypt(str.toString());
//        log.info("加密后:" + md5Str);return md5Str;}/*** 签名校验* @param paramMap* @param signKey* @return*/public static boolean isSignEquals(Map<String, Object> paramMap, String signKey) {String sign = (String)paramMap.get("sign");String md5Str = getSign(paramMap, signKey);if(!sign.equals(md5Str)) {return false;}return true;}/*** 获取时间戳* @return*/public static long getTimestamp() {return new Date().getTime();}/*** 封装同步请求* @param paramMap* @param url* @return*/public static JSONObject sendRequest(Map<String, Object> paramMap, String url){String result = "";try {//封装post参数StringBuilder postdata = new StringBuilder();for (Map.Entry<String, Object> param : paramMap.entrySet()) {postdata.append(param.getKey()).append("=").append(param.getValue()).append("&");}log.info(String.format("--> 发送请求:post data %1s", postdata));byte[] reqData = postdata.toString().getBytes("utf-8");byte[] respdata = HttpUtil.doPost(url,reqData);result = new String(respdata);log.info(String.format("--> 应答结果:result data %1s", result));} catch (Exception ex) {ex.printStackTrace();}return JSONObject.parseObject(result);}
}

mogdodbp配置

#mongo 配置
spring.data.mongodb.uri=mongodb://yourhost:27017/test

创建接口 repository

//实体类,主键类型
@Repository
public interface HospitalRepository extends MongoRepository<Hospital,String> {
}

ApiController

//工具自带跨域解决
@Api(tags = "医院管理API接口")
@RestController
@RequestMapping("/api/hosp")  //请求地址参考文档;要与医院模拟系统那个一致
public class ApiController {@Autowiredprivate HospitalService hospitalService;}

上传医院功能实现

controller层

    @ApiOperation(value = "上传医院")@PostMapping("saveHospital")public Result saveHospital(HttpServletRequest request){//1.获取参数,转换类型Map<String, String[]> parameterMap = request.getParameterMap();Map<String, Object> paramMap = HttpRequestHelper.switchMap(parameterMap);//2. TODO 校验签名//3. 调用接口,数据入库hospitalService.saveHospital(paramMap);return Result.ok();}

serviceImpl层

    //上传医院@Overridepublic void saveHospital(Map<String, Object> parameterMap) {//1.转化参数类型paramMap => Hospital//String paramJsonString = JSONObject.toJSONString(parameterMap);//Hospital hospital = JSONObject.parseObject(paramJsonString, Hospital.class);String paramJsonString = JSONObject.toJSONString(parameterMap);Hospital hospital = JSONObject.parseObject(paramJsonString,Hospital.class);//2.根据hoscode查询医院信息Hospital targetHospital = hospitalRepository.getByHoscode(hospital.getHoscode());//3.存在医院信息进行更新if(targetHospital!=null){hospital.setId(targetHospital.getId());hospital.setCreateTime(targetHospital.getCreateTime());hospital.setUpdateTime(new Date());hospital.setStatus(targetHospital.getStatus());hospital.setIsDeleted(0);hospitalRepository.save(hospital);}else {//4.没有医院信息新增hospital.setCreateTime(new Date());hospital.setUpdateTime(new Date());hospital.setStatus(0);hospital.setIsDeleted(0);hospitalRepository.save(hospital);}}

签名校验

确认签名 (尚医通、医院方),要求签名一致

修改医院系统签名加密方式为MD5

实现医院签名接口方法

三.查询医院

四.科室接口–上传科室

五.查询科室

六.删除科室

七.排班的上传和查询接口

八.排班的分页查询和删除

nextday

一.集成nacos

二.医院列表

三.字典查询

四.医院上线状态

后端接口

前端实现

五.医院详情查询

后端接口

前端实现

nextday

一.Gateway集成


(1)科室信息(大科室与小科室树形展示)
(2)根据医院、科室按排班日期统计号源信息、按日期分页、根据日期判断周几
(3)根据医院、科室、排班日期查询排班列表数据

二.医院排班管理–科室列表

后端接口

前端实现

三.科室排班日期统计

后端接口

前端实现

四.排版详情列表

后端接口

前端实现

尚医通项目(上)

来自atguigu 视频链接

项目介绍

1.概述

尚医通即为网上预约挂号系统,旨在缓解看病难、挂号难的就医难题。随时随地轻松挂号!不用排长队!

2.技术点

核心技术
SpringBoot:简化新Spring应用的初始搭建以及开发过程
SpringCloud:基于Spring Boot实现的云原生应用开发工具,SpringCloud使用的技术:(SpringCloudGateway、Spring Cloud Alibaba Nacos、Spring Cloud Alibaba Sentinel、SpringCloud Task和SpringCloudFeign等)
MyBatis-Plus:持久层框架
Redis:内存缓存
RabbitMQ:消息中间件
HTTPClient: Http协议客户端
Swagger2:Api接口文档工具
Nginx:负载均衡
Lombok:简化实体类的开发
Mysql:关系型数据库
MongoDB:面向文档的NoSQL数据库
Vue.js:web 界面的渐进式框架
Node.js: JavaScript 运行环境
Axios:Axios 是一个基于 promise 的 HTTP 库
NPM:包管理器
Babel:转码器
Webpack:打包工具
Docker :容器技术
Git:代码管理工具

3.项目流程

4.架构

Mybatis-Plus

详情见另一篇文章,链接;
此项目大概涉及以下内容,可针对性学习。

  1. 主键策略 @TableId★

  2. 字段自动填充 @TableField★

  3. 乐观锁 悲观锁

  4. 基础CRUD

  5. 分页查询 ★

  6. 逻辑删除 ★.

    条件构造器

springboot2.1之后内置mysql8.0驱动。

项目基础搭建

1.项目结构及搭建

可以选择新建springboot项目或maven项目两种方式搭建;父模块打包方式为pom,具体搭建看视频吧。

yyds-parent:根目录,管理子模块common:公共模块父节点common-util:工具类模块,所有模块都可以依赖于它rabbit-util:rabbitmq业务封装service-util:service服务的工具包,包含service服务的公共配置类,所有service模块依赖于它hospital-manage:医院接口模拟端model:实体类模块server-gateway:服务网关service:api接口服务父节点service-cmn:字典api接口服务service-hosp:医院api接口服务service-order:订单api接口服务service-oss:文件存储api接口服务service-sms:短信api接口服务service-statistics:统计api接口服务service-task:定时任务服务service-user:用户api接口服务service-client:feign服务调用父节点service-cmn-client:字典api接口service-hosp-client:医院api接口service-order-client:订单api接口service-user-client:用户api接口
搭建模块
yygh_parentcommoncommon_utilservice_utilmodelserviceservice_hospservice_user

2.集成Swagger2

一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务

常用注解

举例:
@EnableSwagger2    声明支持Swagger。声明在配置类“@Configuration”@Api(description = "医院设置接口")   定义controller层接口说明@ApiOperation(value = "医院设置列表")   定义方法说明@ApiModelProperty(value = "姓名")   定义实体属性说明@ApiParam(name = "id", value = "讲师ID", required = true)   定义参数说明

依赖及搭建步骤

因为swagger测试只有service模块里的服务用得到

在common模块service_util的config包中新建配置类Swagger2Config

@Configuration
@EnableSwagger2
public class Swagger2Config {@Beanpublic Docket webApiConfig(){return new Docket(DocumentationType.SWAGGER_2).groupName("webApi").apiInfo(webApiInfo()).select()//只显示api路径下的页面,包含/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("xiaoxin", "=blog", "839623440@qq.com")).build();}private ApiInfo adminApiInfo(){return new ApiInfoBuilder().title("后台管理系统-API文档").description("本文档描述了后台管理系统微服务接口定义").version("1.0").contact(new Contact("xiaoxin", "=blog", "839623440@qq.com")).build();}
}

依赖
common下的pom.xml

        <!--swagger2--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId></dependency>

service 项目添加依赖

<dependency><groupId>com.atguigu</groupId><artifactId>service_utils</artifactId><version>0.0.1-SNAPSHOT</version>
</dependency>

测试界面访问

需要在启动类上添加@ComponentScan(basePackages = “com.atguigu”)
否则扫描不到配置类

http://localhost:8201/swagger-ui.html

3.统一结果返回

common模块下创建子模块common_utils
1.状态码定义

2.统一结果类

@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;}
//结果数据和状态码、信息的添加,其中resultCodeEnum可以是如ResultCodeEnum.SUCCESS/ResultCodeEnum.FAILpublic 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);}//可以链式调用(返回的Result对象),可重新set返回信息,不在单纯是fail的失败public Result<T> message(String msg){this.setMessage(msg);return this;}

4.统一异常处理

@ControllerAdvice spring AOP面向切面编程,对Controller进行切面环绕。
作用:全局异常处理、全局数据预处理、全局数据绑定
@ExceptionHandler (Exception.class) 异常拦截器(自定义异常处理器),需要结合@ControllerAdvice一起使用

common/common_util/exception包

系统异常

@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)@ResponseBodypublic Result error(Exception e){//e.printStackTrace();return Result.fail(e.getmessage());}@ExceptionHandler(YyghException.class)@ResponseBodypublic Result error(YyghException e){e.printStackTrace();return Result.fail();}
}

特殊异常

自定义异常

有些系统异常不能满足我们的需求
返回结果和自定义异常分开,这里自定义异常new了之后toString打印在了控制台;感觉这部分不是很好,后头借鉴一下谷粒学苑的吧

@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() +'}';}
}

思考
对于枚举类状态码信息的操作;感觉在下面这种情况
if (resultMap == null) {//出错
return Result.fail().message(“支付出错”);
}
可以throw,new自定义异常类,返回错误信息(需要在enum中在新建一个对象)
也可以在返回结果类时重新set错误信息;

5.统一日志处理(了解)

spring boot内部使用Logback作为日志实现的框架。

配置日志级别日志记录器(Logger)的行为是分等级的。如下表所示:分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL默认情况下,spring boot从控制台打印出来的日志级别只有INFO及以上级别,可以配置日志级别# 设置日志级别
logging.level.root=WARN
这种方式只能将日志打印在控制台上sout 会产生io,需要删掉

日志配置

<?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:/JavaStudy/graduationDesign/yygh_log" /><!-- 彩色日志 --><!-- 配置格式变量: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>

医院设置做什么:不同医院和预约挂号平台建立链接;每个医院有自己的编号。

医院设置主要是用来保存开通医院的一些基本信息,每个医院一条信息,保存了医院编号(平台分配,全局唯一)和接口调用相关的签名key等信息,是整个流程的第一步,只有开通了医院设置信息,才可以上传医院相关信息。
医院被锁定后便不能再上传信息。

医院设置接口

数据模型


提示:使用mp,在service层调用basemapper不用再注入,因为继承的ServiceImpl已经注入了

service_hosp模块

1.带条件带分页查询接口

添加分页插件

@Configuration
@MapperScan("com.atguigu.yygh.hosp.mapper")
public class HospConfig {/*** 分页插件**/@Beanpublic PaginationInterceptor paginationInterceptor(){return new PaginationInterceptor();}
}
**HospitalSetController层接口**
@Api(tags = "医院设置管理")
@RestController
@RequestMapping("/admin/hosp/hospitalSet")
public class HospitalSetController 

vo对象

HospitalSetQueryVo
包含医院名称和编号
	/*** 条件查询带分页* 注意:@PostMapping 才能获取@RequestBody传来的json数据* 		@responseBody底层是使用Jackson来完成对象到json的转换*      @RequestBody(required = false) 代表这个数据可以不传* @param current:当前页* @param limit:每页显示几条数据* @param hospitalSetQueryVo:查询条件(医院编号、医院名称(模糊查询))* @return*/@ApiOperation(value = "条件查询带分页")@PostMapping("findPageHospSet/{current}/{limit}")public Result findPageHospSet(@PathVariable("current") long current,@PathVariable("limit") long limit,@RequestBody(required = false) HospitalSetQueryVo hospitalSetQueryVo){//创建page对象,传递当前页,每页记录数Page<HospitalSet> page = new Page<>(current,limit);QueryWrapper<HospitalSet> wrapper = new QueryWrapper<>();if (!StringUtils.isEmpty(hospitalSetQueryVo)) {String hosname = hospitalSetQueryVo.getHosname();String hoscode = hospitalSetQueryVo.getHoscode();//构造条件wrapper.like(!StringUtils.isEmpty(hosname),"hosname",hosname).eq(!StringUtils.isEmpty(hoscode),"hoscode", hoscode);//调用方法实现分页的查询Page<HospitalSet> hospitalSetPage = hospitalSetService.page(page, wrapper);//返回结果return Result.ok(hospitalSetPage);}

page对象常用属性;(mybatis-plus封装的)

2.新增接口

添加MD5工具类

controller层接口

    @ApiOperation(value = "添加医院设置")@PostMapping("/saveHospitalSet")public Result saveHospitalSet(@RequestBody HospitalSet hospitalSet) {// 设置状态 1使用 0不能使用hospitalSet.setStatus(1);// 设置签名密钥Random random = new Random();hospitalSet.setSignKey(MD5.encrypt(System.currentTimeMillis() + "" + random.nextInt(1000)));boolean save = hospitalSetService.save(hospitalSet);return save?Result.ok():Result.fail();}

数据库中设置了时间、状态的默认值,所以不用添加;但是还是可以做个mp的自动填充,修改时时间会自动改变等等。

3.修改接口

说明:此接口同时需要实现根据id查询,以实现修改时的数据回显。

	/***根据id获取医院设置* @param id 医院id* @return 医院设置信息*/@GetMapping("getHospitalSetById/{id}")public Result getHospitalSetById(@PathVariable("id") Long  id){HospitalSet hospitalSet = hospitalSetService.getById(id);return Result.ok(hospitalSet);}/***修改医院设置* @param hospitalSet 修改后医院设置信息* @return 修改的结果*/@PostMapping("updateHospitalSet")public Result updateHospitalSet(@RequestBody HospitalSet hospitalSet){boolean flag = hospitalSetService.updateById(hospitalSet);return flag?Result.ok():Result.fail();}

4.批量删除接口

删除

    /*** 逻辑删除医院设置* @return*/@ApiOperation(value = "逻辑删除医院设置")@DeleteMapping("/{id}")public Result removeHospSet(@PathVariable("id") long id) {boolean flag = hospitalSetService.removeById(id);if (flag) {return Result.ok();} else {return Result.fail();}}

批量删除

	/*** 批量删除医院设置* @param idList 要删除的id集合* @return 返回批量删除的结果*/@DeleteMapping("batchRemove")public Result batchRemoveHospitalSet(@RequestBody List<Long> idList){boolean flag = hospitalSetService.removeByIds(idList);return flag?Result.ok():Result.fail();}

弹幕说集合没有实现序列化,分布式环境会接受不了数据?

5.锁定医院设置

	/*** 医院设置锁定和解锁的* @param id 医院id* @param status 该医院的状态* @return*/@PutMapping("lockHospitalSet/{id}/{status}")public Result lockHospitalSet(@PathVariable Long id,@PathVariable Integer status){//先根据id查询医院设置信息HospitalSet hospitalSet = hospitalSetService.getById(id);//设置状态hospitalSet.setStatus(status);boolean flag = hospitalSetService.updateById(hospitalSet);return flag?Result.ok():Result.fail();}

6.发送签名密钥

烂尾了。

    @ApiOperation(value = "发送签名秘钥")@PutMapping("/sendKey/{id}")public Result sendKey(@PathVariable Long id) {HospitalSet hospitalSet = hospitalSetService.getById(id);String signKey = hospitalSet.getSignKey();String hoscode = hospitalSet.getHoscode();//TODO 发送短信return Result.ok();}
}

后台前端

vue-element-admin GitHub地址
vue-admin-template GitHub地址
项目在线预览:地址
elementui(基于vue2x) 地址
建议:你可以在 vue-admin-template 的基础上进行二次开发,把 vue-element-admin当做工具箱,想要什么功能或者组件就去 vue-element-admin 那里复制过来

//克隆工程  有关git可以看[这篇文章]()
git clone .git
//下载相关依赖
npm install
//运行npm run dev

如果下载以依赖出现node-sass类错,尝试先执行下面的代码

npm i -g node-sass --sass_binary_site=/

一.结构

vue-damin-temeplatebulid:构建相关config:全局配置src:源代码api:所有请求assets:主题 字体等静态资源components:全局公共组件icons:项目所有svg iconsrouter:路由store:全局store管理styles:全局样式utils:全局公用方法views:视图	App.vue:入口页面main.js:入口 加载组件 初始化等permission.js:权限管理static:静态资源.babelrc:babel-loader配置.eslintrc.js:eslint配置项.gitignore:git忽略项package.json:依赖管理

二.项目中的重要文件

package.js
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.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

注意

修改配置文件中的语法检查,如果不能接受一直报错;改为false

实现前面所写接口的前端页面

三.登录简易改造

前端

把登陆写死的,不用再请求。

  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':'.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 }) {// return new Promise(resolve => {commit('SET_TOKEN', '')removeToken()resolve()// })}}

前台开发步骤


修改请求路径
VUE_APP_BASE_API = 'http://localhost:8201/
修改api接口方法 请求路径

跨域问题解决方案

在controller层中添加注解@CrossOrigin

四.医院设置列表

1.添加路由,创建页面文件

  {path: '/yygh/hospset',component: Layout,redirect: '/yygh/hospset/list',name: 'hosp',meta: { title: '医院管理', icon: 'el-icon-s-help' },children: [{path: 'list',name: '医院设置列表',component: () => import('@/views/yygh/hospset/list'),meta: { title: '医院设置列表', icon: 'table' }},{path: 'add',name: '医院设置表单',component: () => import('@/views/yygh/hospset/add'),meta: { title: '添加医院设置', icon: 'tree' }}]},

2.创建API

src\api\hosp.js

import request from '@/utils/request'
//提取请求路径
const api_name = '/admin/hosp/hospitalSet'export default{//分页条件查询医院设置pageQuery(page,limit,searchObj){return request({url: `${api_name}/pageQuery/${page}/${limit}`,//插值表达式method: 'post',data:searchObj  //使用json方式传递数据;如果写param,则前面不用加data})}
}

3.编写页面

添加分页元素

slot-scope代表整个表格;.代表每一行
prop属性

<template><div class="app-container">医院设置列表<el-tablev-loading="listLoading":data="list"element-loading-text="数据加载中"borderfithighlight-current-row><el-table-column label="序号" width="70" align="center"><template slot-scope="scope">{{ (page - 1) * limit + scope.$index + 1 }}</template></el-table-column><!-- 字段名要与对象属性一致 --><el-table-column prop="hosname" label="医院名称" width="180" /><el-table-column prop="hoscode" label="医院编号" width="160" /><el-table-column prop="apiUrl" label="地址" width="200" /><el-table-column prop="contactsName" label="联系人" /><el-table-column prop="status" label="状态"><template slot-scope="scope"> {{ scope.row.status === 1 ? "可用" : "不可用" }} <!-- === 比较类型也比较值 --></template></el-table-column><el-table-column label="操作" width="200" align="center"><template slot-scope="scope"><router-link :to="'/yygh/hospset/edit/' + scope.row.id"><el-button type="primary" size="mini" icon="el-icon-edit">修改</el-button></router-link><el-buttontype="danger"size="mini"icon="el-icon-delete"@click="removeDataById(scope.row.id)">删除</el-button></template></el-table-column></el-table></div>
</template>

底部分页组件

    <!-- 分页 --><el-pagination:current-page="page":page-size="limit":total="total"style="padding: 30px 0; text-align: center;"layout="total, prev, pager, next, jumper"//会自动传当前页,封装好的@current-change="fetchData"/>

分页查询表单

           医院设置列表<!--查询表单inline一行显示--><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="fetchData()">查询</el-button><el-button type="default" @click="resetData()">清空</el-button></el-form>

页面逻辑js

//引入接口
imoort pageQuery  from '@/api/hospset'
//------------------
data() {return {listLoading: true, //表格加载状态list: [], //表格数据page: 1, //当前页limit: 10, //每页记录数total:0,//总记录数searchObj: {} //查询条件};},created() {this.fetchData();},methods: {//分页条件查询 添加默认传参 es6新特性;fetchData(page=1) {this.page = page//hospsetApi为引入的那个接口名,里面包含了那个文件里的所有接口方法hospsetApi.pageQuery(this.page, this.limit, this.searchObj).then(response => {console.log(response);this.list = response.data.pageModel.records;this.total = response.data.pageModel.totalthis.listLoading = false;});},//清空resetData() {this.searchObj = {};this.fetchData();},}

五.删除医院设置

1. 添加api接口

    //医院设置删除removeById(id){return request({url: `${api_name}/${id}`,//插值表达式method: 'delete'})},

2.页面js

删除和修改按钮元素,在前面已经写了

 <el-table-column label="操作" width="200" align="center"><template slot-scope="scope"><router-link :to="'/yygh/hospset/edit/' + scope.row.id"><el-button type="primary" size="mini" icon="el-icon-edit">修改</el-button></router-link><el-buttontype="danger"size="mini"icon="el-icon-delete"<!--这里会传入要删除的id-->@click="removeDataById(scope.row.id)">删除</el-button></template></el-table-column>
        //删除removeDataById(id) {this.$confirm("此操作将永久删除该数据, 是否继续?", "提示", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",//确定执行then的,取消执行catch的}).then(() => {//删除hospsetApi.removeById(id).then((response) => {//刷新页面;也可以用window.location.reload();this.fetchData();//try传参current});this.$message({type: "success",message: "删除成功!",});}).catch(() => {this.$message({type: "info",message: "已取消删除",});});},

六.新增医院设置

1. 实现接口API

    //新增医院设置save(hospitalSet){return request({url: `${api_name}/save`,//插值表达式method: 'post',data:hospitalSet})},

2.编写页面(add.vue)

新增与修改共用一个页面

表单元素

<template><div class="app-container">医院设置表单<el-form label-width="120px"><el-form-item label="医院名称"><el-input v-model="hospset.hosname"/></el-form-item><el-form-item label="医院编号"><el-input v-model="hospset.hoscode"/></el-form-item><el-form-item label="api地址"><el-input v-model="hospset.apiUrl"/></el-form-item><el-form-item label="联系人"><el-input v-model="hospset.contactsName"/></el-form-item><el-form-item label="电话"><el-input v-model="hospset.contactsPhone"/></el-form-item><el-form-item><el-button :disabled="saveBtnDisabled" type="primary" @click="saveOrUpdate">保存</el-button></el-form-item></el-form></div>
</template>

页面js

页面跳转用 r o u t e r ,获取元素用 router,获取元素用 router,获取元素用route;注意区别

<script>
import hospsetApi from "@/api/yygh/hospset";
export default {data() {return {hospset: {}, //表单对象saveBtnDisabled: false //按钮是否不可操作};},created() {},methods: {//保存saveOrUpdate() {//新增this.saveHospset();},//新增saveHospset() {hospsetApi.save(this.hospset).then(response => {this.$message({type: "success",message: "新增成功!"});//路由页面跳转this.$router.push({path:'/yygh/hospset/list'})});}}
};
</script>

七.医院设置修改

修改元素在前面已经写好

1.隐藏路由

什么是隐藏路由

      {//占位符设置什么名字,取值时就用什么path: 'edit/:id',name: 'HospSetEdit',component: () => import('@/views/yygh/hospset/add'),meta: { title: '编辑医院设置', noCache: 'tree' },hidden: true}

2.修改时的数据回显

1.添加api
    //根据id进行查询getById(id){return request({url: `${api_name}/getById/${id}`,//插值表达式method: 'get'})},
2.页面js
          <router-link :to="'/yygh/hospset/edit/' + scope.row.id"><el-button type="primary" size="mini" icon="el-icon-edit">修改</el-button></router-link>created () {//通过路由获取id(占位符),判断id数据是否存在(有参数且有id)if(this.$route.params&&this.$route.params.id){//注意,获取参数用route,不是router//console.log(this.$route.params.id);//数据回显hospsetApi.getById(this.$route.params.id).then(response=>{this.hospset = response.data.hospitalSet})}},

3.实现修改保存

1.新增api
    //修改医院设置update(hospitalSet){return request({url: `${api_name}/update`,method: 'post',data: hospitalSet  //用json})},
2.页面js
        methods: {//保存saveOrUpdate(){//新增与修改的区别:vu对象是否存在idthis.saveBtnDisabled = trueif (!this.hospset.id) {//新增this.saveHospset()} else {//编辑this.updateHospset()}},//修改updateHospset(){hospsetApi.update(this.hospset).then(response=>{this.$message({type: "success",message: "修改成功!",});//页面跳转,用routerthis.$router.push({path:'/yygh/hospset/list'})});},

思考:也可以不写两个方法(添加,修改);直接都传一个对象给后端,用mp的saveOrUpdate方法或者自己写一个。

八.医院设置批量删除

实现步骤
给表格添加复选框,可以拿到选择的对象
点击“批量删除”按钮,获取所有选择对象的id,存入集合

1.添加API

    //批量删除医院设置batchRemove(idList){return request({url: `${api_name}/batchRemove`,//插值表达式method: 'delete',data: idList})},

2.编写页面

只是添加了批量删除复选框

        <!-- 工具条 --><div><el-button type="danger" size="mini" @click="removeRows()">批量删除</el-button></div><!-- alt + shift + f 格式化页面 --><!-- 复选框 批量修改 @selection-change="handleSelectionChange" --><el-tablev-loading="listLoading":data="list"element-loading-text="数据加载中"borderfithighlight-current-row@selection-change="handleSelectionChange">   <!--selection表示复选框--><el-table-column type="selection" width="55" />

3.页面js

复选框选择的method实现

    data() {return {listLoading: true, //表格加载状态list: [], //表格数据page: 1, //当前页limit: 5, //每页记录数total: 0,searchObj: {}, //查询条件multipleSelection: [], // 批量选择中选择的记录列表(批量删除)};},...methods: {....//复选框选择方法;每次选择复选框会触发,传入对象(每行一个对象)handleSelectionChange(selection) {console.log(selection);this.multipleSelection = selection;},

批量删除method实现
记得判断一下如果没选择,提醒用户无法删除

//批量删除removeRows() {this.$confirm("此操作将永久删除该数据, 是否继续?", "提示", {confirmButtonText: "确定",cancelButtonText: "取消",type: "warning",}).then(() => {//1.创建参数对象idListlet idList = [];//2.遍历multipleSelection对象,取出idfor (let i = 0; i < this.multipleSelection.length; i++) {let hospset = this.multipleSelection[i];let id = hospset.id;//3.存入idListidList.push(id);}console.log(idList);//4.调接口进行批量删除hospsetApi.batchRemove(idList).then((response) => {this.fetchData();this.$message({type: "success",message: "删除成功!",});});}).catch(() => {this.$message({type: "info",message: "已取消删除",});});},

九.锁定和取消锁定

1.新增api

    //锁定和取消锁定lockHospSet(id, status) {return request({url: `${api_name}/lockHospitalSet/${id}/${status}`,method: 'put'})},

2.页面js

之前的页面元素(删除、保存、修改、锁定)

            <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><el-button v-if="scope.row.status==1" type="danger" size="mini"    icon="el-icon-delete" @click="lockHospSet(scope.row.id,0)">锁定</el-button><el-button v-if="scope.row.status==0" type="primary" size="mini"    icon="el-icon-delete" @click="lockHospSet(scope.row.id,1)">取消锁定</el-button><router-link :to="'/hospSet/edit/'+scope.row.id"><el-button type="primary" size="mini" icon="el-icon-edit"></el-button></router-link></template></el-table-column>
//锁定和取消锁定lockHospSet(id,status){hospset.lockHospSet(id,status).then(response=>{//刷新页面this.getList()})},

数据字典

数据字典就是管理系统常用的分类数据或者一些固定数据,例如:省市区三级联动数据、民族数据、行业数据、学历数据等,由于该系统大量使用这种数据,所以我们要做一个数据管理方便管理系统数据,一般系统基本都会做数据管理。
数据字典就是系统中数据展示文字和编码的映射关系

一.表结构及分析

二.service_cmn模块搭建

三.数据字典列表

根据element组件要求,返回列表数据必须包含hasChildren字典;即是否包含子节点

在model模块查看Dict类

    @ApiModelProperty(value = "是否包含子节点")//表示在数据库中并不存在,不写下面的注解会报错的@TableField(exist = false)private boolean hasChildren;

后端实现

1.添加config配置类
@Configuration
//开启事务管理?
@EnableTransactionManagement
@MapperScan("com.atguigu.yygh.cmn.mapper")
public class CmnConfig {
}
2.实现接口

DictController

@Api(description = "数据字典接口")
@RestController
@RequestMapping("/admin/cmn/dict")
@CrossOrigin
public class DictController {@Autowiredprivate DictService dictService;//根据数据id查询子数据列表@ApiOperation(value = "根据数据id查询子数据列表")@GetMapping("findChildData/{id}")public R findChildData(@PathVariable Long id){List<Dict> list = dictService.findChildData(id);return R.ok().data("list",list);}}

DictServiceImpl
查询:select * from dict where parent_id = id;
下面代码可以考虑用stream优化(for循环里有数据库操作)


@Service
public class DictServiceImplextends ServiceImpl<DictMapper, Dict>implements DictService {//根据数据id查询子数据列表@Overridepublic List<Dict> findChildData(Long id) {//1.根据父id查询子级别数据集合QueryWrapper<Dict> wrapper = new QueryWrapper<>();wrapper.eq("parent_id",id);List<Dict> dictList = baseMapper.selectList(wrapper);//2.遍历查询是否有子数据for (Dict dict : dictList) {boolean hasChildren = this.isChildren(dict.getId());dict.setHasChildren(hasChildren);}return dictList;}//查询是否有子数据,方法提取private boolean isChildren(Long id) {QueryWrapper<Dict> wrapper = new QueryWrapper<>();wrapper.eq("parent_id",id);Integer count = baseMapper.selectCount(wrapper);return count > 0;}
}
测试结果

前端实现

1.创建路由和页面文件

alwaysShow: true, //总是展示一级菜单
不加这个属性。当只有一个children,便只显示childen了

  {path: '/cmn',component: Layout,redirect: '/cmn/list',name: '数据管理',alwaysShow: true, //总是展示一级菜单meta: { title: '数据管理', icon: 'example' },children: [{path: 'list',name: '数据字典',component: () => import('@/views/yygh/dict/list'),meta: { title: '数据字典', icon: 'table' }}]},

2.创建API接口

api/dict.js

import request from '@/utils/request'
//提取请求路径
const api_name = '/admin/cmn/dict'export default{//根据数据id查询子数据列表findChildData(id){return request({url: `${api_name}/findChildData/${id}`,//插值表达式method: 'get'})},}
3.添加页面元素

注意:若页面显示不对,可能是element-ui版本过低,修改版本后,删除node-moudle里的,再重新下载。
lazy属性为懒加载,只加载一级节点 ,通过 :load=“load” 方法加载子节点数据

<template><div class="app-container"> <!-- lazy 只加载一级节点 :load="load"  --><el-table :data="list" style="width: 100%" row-key="id" border lazy <!--load方法,封装的,会递归查询。-->:load="load"<!--hasChildren用处在这-->:tree-props="{children: 'children', hasChildren: 'hasChildren'}"><el-table-column prop="name" label="名称" width="150"></el-table-column><el-table-column prop="dictCode" label="编码" width="150"></el-table-column><el-table-column prop="value" label="值" width="150"></el-table-column><el-table-column prop="createTime" label="创建时间"></el-table-column></el-table></div>
</template>
4.页面js
<script>
import dictApi from "@/api/yygh/dict"
export default{data() {return {list: []  //字典数据集合   }},created() {//全部分类id为1this.getDate(1)},methods: {//初始化查询方法getDate(id){dictApi.findChildData(id).then(response=>{this.list = response.data.list})},//树形节点加载方法,tree为节点对象,通过resolve()加载数据load(tree, treeNode, resolve){//传入结点id,继续查询子节点dictApi.findChildData(tree.id).then(response=>{//参考官方文档resolve(response.data.list)})}}
}</script>

此时后端已经写了两个模块,对应不同端口,发请求的请求地址已经出现问题
解决方案:使用nginx作请求转发;统一设置请求路径为nginx的路径(VUE_APP_BASE_API = ‘http://localhost:9001’)

Nginx

1.nginx的下载、安装、启动
2.修改配置文件

所在位置为nginx/conf;一定要放在http块里

	server {#监听端口listen       9001;#nginx所在主机域名server_name  localhost;location ~ /hosp/ {      #转发     proxy_pass http://localhost:8201;}location ~ /cmn/ {     #转发      proxy_pass http://localhost:8202;}
}

四.EasyExcel操作

1.概述

Java领域解析、生成Excel比较有名的框架有Apache poi、jxl等。但他们都存在一个严重的问题就是非常的耗内存。面对高访问高并发,一定会OOM或者JVM频繁的full gc (重jc)。

EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
github地址

文档地址

2.测试demo

写操作

导入依赖

<!--EasyExcel-->
<dependencies><!-- .alibaba/easyexcel --><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.1.1</version></dependency>
</dependencies>

实体对象

@Data
public class Stu {//设置表头名称@ExcelProperty("学生编号")private int sno;//设置表头名称@ExcelProperty("学生姓名")private String sname;}

测试类

public class WriteTest {public static void main(String[] args) {String fileName = "E:\\test.xlsx";EasyExcel.write(fileName,Stu.class)//文件名/流,class类型.sheet("学生信息")//设置sheet名.doWrite(data());//传入要写的list集合data}//循环设置要添加的数据,最终封装到list集合中private static List<Stu> data() {List<Stu> list = new ArrayList<Stu>();for (int i = 0; i < 10; i++) {Stu data = new Stu();data.setSno(i);data.setSname("lucy"+i);list.add(data);}return list;}
}
读操作

改造实体类,指定对应关系

@Data
public class Stu {//设置表头名称,指定映射关系(第0列);index即为索引@ExcelProperty(value = "学生编号",index = 0)private int sno;//设置表头名称,指定映射关系(第1列)@ExcelProperty(value = "学生姓名",index = 1)private String sname;}

创建监听器

public class ExcelListener extends AnalysisEventListener<Stu> {//一行一行去读取excle内容,从第二行开始@Overridepublic void invoke(Stu stu, AnalysisContext analysisContext) {System.out.println("stu = " + stu);}//读取excel表头信息@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {System.out.println("表头信息:"+headMap);}//读取完成后执行@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {}
}

测试

public class ReadTest {public static void main(String[] args) {String fileName = "E:\\test.xlsx";//文件名,class类型,监听器EasyExcel.read(fileName,Stu.class,new ExcelListener()).sheet().doRead();}}

总结 :读操作需要配置监听器

五.数据字典导出、导入

1.导出

后端实现

添加依赖

在service_cmn里

<!--EasyExcel-->
<dependencies><!-- .alibaba/easyexcel --><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.1.1</version></dependency>
</dependencies>

返回前端显示对象DictEeVo

@Data
public class DictEeVo {//对应excel表格的行开头信息@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;}

DictController

	/*** 导出数据字典接口* @param response 响应输出流,将Excel返回;方便下载操作* @return 导出结果*/@GetMapping("exportData")public void exportDict(HttpServletResponse response){dictService.exportDictData(response);}

DictServiceImpl

	//导出数据字典接口@Overridepublic void exportDictData(HttpServletResponse response) {//设置下载信息response.setContentType("application/vnd.ms-excel");//文件类型response.setCharacterEncoding("utf-8");//编码//URLEncoder.encode(“哈哈”,“utf-8”);若是英文名,要这样指定编码格式String fileName = "DataDict-"+ UUID.randomUUID();//头信息意思:以下载方式打开response.setHeader("Content-disposition", "attachment;filename="+ fileName + ".xlsx");//查询数据库List<Dict> dictList = baseMapper.selectList(null);//Dict-->DictVoList<DictEeVo> dictEeVoList = new ArrayList<>();for (Dict dict : dictList) {DictEeVo dictEeVo = new DictEeVo();BeanUtils.copyProperties(dict,dictEeVo);dictEeVoList.add(dictEeVo);}//调用方法进行写操作try {EasyExcel.write(response.getOutputStream(), DictEeVo.class).sheet("数据字典").doWrite(dictEeVoList);} catch (IOException e) {throw new YyghException("导出异常",500);}}

记得重温下javaweb写文件…

前端实现

添加页面元素

<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>

页面js

因为不用返回数据,只是发请求,没必要再写在api接口那了。

            //导出数据exportData(){//window.open("http://localhost:8202/admin/cmn/dict/exportData")//照葫芦画瓢。从配置文件读取window.open(`${process.env.VUE_APP_BASE_API}admin/cmn/dict/exportData`)
//window.location.href("http://localhost:8202/admin/cmn/dict/exportData")也可;还可以直接在页面写个a标签,直接在新的页面打开}

2.导入

后端实现

controller层

    @ApiOperation(value = "导入字典数据")@PostMapping("importData")public R importData(MultipartFile file){dictService.importData(file);return R.ok();}

实现监听器

另一种注入dictMapper方式。

public class DictListener extends AnalysisEventListener<DictEeVo> {
private DictMapper dictMapper;public DictListener(DictMapper dictMapper) {this.dictMapper = dictMapper;
}//一行一行读取
@Override
public void invoke(DictEeVo dictEeVo, AnalysisContext analysisContext) {//调用方法添加数据库Dict dict = new Dict();BeanUtils.copyProperties(dictEeVo,dict);dictMapper.insert(dict);
}@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {}

}`

@Component //
public class DictListener extends AnalysisEventListener<DictEeVo> {@Autowiredprivate DictMapper dictMapper;@Overridepublic void invoke(DictEeVo dictEeVo, AnalysisContext analysisContext) {//转换类型Dict dict = new Dict();BeanUtils.copyProperties(dictEeVo,dict);//设置默认值dict.setIsDeleted(0);dictMapper.insert(dict);}@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {}
}

service层

    @AutowiredDictListener dictListener;//导入字典数据@Overridepublic void importData(MultipartFile file) {try {InputStream inputStream = file.getInputStream();EasyExcel.read(inputStream,DictEeVo.class,dictListener).sheet().doRead();} catch (IOException e) {e.printStackTrace();throw new YyghException(20001,"导入数据失败");}}

实体类修改

@Data
@ApiModel(description = "数据字典")
@TableName("dict")
public class Dict {@ApiModelProperty(value = "id")private Long id;@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 = "创建时间")@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 boolean hasChildren;@ApiModelProperty(value = "其他参数")@TableField(exist = false)private Map<String,Object> param = new HashMap<>();}

给的建表sql文件is_deleted默认值为1;记得修改为0
思考,向数据库导入excel中的数据,如果不是覆盖,会越加越多,如何解决?若是可以覆盖,又会被随便修改数据。。

前端实现

添加页面元素

        <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><el-button type="text" @click="importData"><i class="fa fa-plus" />导入</el-button></div></div>

由于无法使用ajax传递文件,所以使用element的组件

上传组件及对话框dialog

        <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="BASE_API" class="upload-demo"><el-button size="small" type="primary">点击上传</el-button><div slot="tip" class="el-upload__tip">只能上传xls文件,且不超过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>

页面js

        data() {return {list: [],  //字典数据集合 dialogImportVisible: false,  //对话框,默认不显示;true时,弹出BASE_API: `${process.env.VUE_APP_BASE_API}admin/cmn/dict/importData` //请求地址}},created() {this.getDate(1)},methods: {....//打开导入对话框importData(){this.dialogImportVisible = true},//上传成功后的操作onUploadSuccess(){//1.关闭对话框this.dialogImportVisible = false//2.上传成功提示this.$message({type: "success",message: "上传成功!"})//3.刷新表格this.getDate(1)}}

六.数据字典添加缓存

添加缓存时机:查询量大,且不经常改变,可以添加redis缓存,提高查询效率。

redis详细文章可以看这个

Spring Cache 是一个非常优秀的缓存组件。自Spring 3.1起,提供了类似于@Transactional注解事务的注解Cache支持,且提供了Cache抽象,方便切换各种底层Cache(如:redis)
使用Spring Cache的好处:
1,提供基本的Cache抽象,方便切换各种底层Cache;
2,通过注解Cache可以实现类似于事务一样,缓存逻辑透明的应用到我们的业务代码上,且只需要更少的代码就可以完成;
3,提供事务回滚时也自动回滚缓存;
4,支持比较复杂的缓存逻辑;
因为缓存也是公共使用,所有的service模块都有可能使用缓存,所以我们把依赖与部分配置加在service-util模块,这样其他service模块都可以使用了

1.整合redis

添加依赖在common_utils模块

    <dependencies><!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- spring2.X集成redis所需common-pool2--><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.6.0</version></dependency></dependencies>

添加redis配置类

使用redisTemplate,会出现序列化转换 观察不便的问题;其实也可以用StringRedisTemplate

@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 方法是否存在缓存注解。如果找到这样的一个注释, 自动创建一个代理拦截方法调用和处理相应的缓存行为。

配置文件

#redis
spring.redis.host=
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

2.同步缓存

写操作时,清空相关缓存。再次查询时同步

常用缓存标签

缓存@Cacheable
根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。
属性值如下:
value: 缓存名,必填,它指定了你的缓存存放在哪块命名空间
cacheNames: 与 value 差不多,二选一即可
key: 可选属性,可以使用 SpEL 标签自定义缓存的key

缓存@CachePut 使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。
属性值同上

缓存@CacheEvict 使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法
allEntries 是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存
beforeInvocation 是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存

添加缓存

/*** 根据数据id查询子数据列表* @param id* @return* keyGenerator 生成key的规则,在redisConfig写好过的*/@Cacheable(value = "dict", keyGenerator = "keyGenerator")@Overridepublic List<Dict> findChildData(Long id) 
/*** 数据字典导入* @param file*/@CacheEvict(value = "dict", allEntries = true)@Overridepublic void importData(MultipartFile file)

mongodb

详细见这篇文章吧 链接

医院模拟系统接口开发

一.部署医院模拟系统

主要完成医院、科室、排版接口

父工程下创建 hospital-manage 模块

从资料里复制过来

二.上传(保存)医院信息接口

添加工具类

MD5

public final class MD5 {public static String encrypt(String strSrc) {try {char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8','9', 'a', 'b', 'c', 'd', 'e', 'f' };byte[] bytes = strSrc.getBytes();MessageDigest md = MessageDigest.getInstance("MD5");md.update(bytes);bytes = md.digest();int j = bytes.length;char[] chars = new char[j * 2];int k = 0;for (int i = 0; i < bytes.length; i++) {byte b = bytes[i];chars[k++] = hexChars[b >>> 4 & 0xf];chars[k++] = hexChars[b & 0xf];}return new String(chars);} catch (NoSuchAlgorithmException e) {e.printStackTrace();throw new RuntimeException("MD5加密出错!!+" + e);}}}

HttpRequestHelper

用于发送http请求给hosp模块

@Slf4j
public class HttpRequestHelper {public static void main(String[] args) {Map<String, Object> paramMap = new HashMap<>();paramMap.put("d", "4");paramMap.put("b", "2");paramMap.put("c", "3");paramMap.put("a", "1");paramMap.put("timestamp", getTimestamp());log.info(getSign(paramMap, "111111111"));}/**** @param paramMap* @return*/public static Map<String, Object> switchMap(Map<String, String[]> paramMap) {Map<String, Object> resultMap = new HashMap<>();for (Map.Entry<String, String[]> param : paramMap.entrySet()) {resultMap.put(param.getKey(), param.getValue()[0]);}return resultMap;}/*** 请求数据获取签名* @param paramMap* @param signKey* @return*/public static String getSign(Map<String, Object> paramMap, String signKey) {if(paramMap.containsKey("sign")) {paramMap.remove("sign");}String md5Str = MD5.encrypt(signKey);
//        TreeMap<String, Object> sorted = new TreeMap<>(paramMap);
//        StringBuilder str = new StringBuilder();
//        for (Map.Entry<String, Object> param : sorted.entrySet()) {
//            str.append(param.getValue()).append("|");
//        }
//        str.append(signKey);
//        log.info("加密前:" + str.toString());
//        String md5Str = MD5.encrypt(str.toString());
//        log.info("加密后:" + md5Str);return md5Str;}/*** 签名校验* @param paramMap* @param signKey* @return*/public static boolean isSignEquals(Map<String, Object> paramMap, String signKey) {String sign = (String)paramMap.get("sign");String md5Str = getSign(paramMap, signKey);if(!sign.equals(md5Str)) {return false;}return true;}/*** 获取时间戳* @return*/public static long getTimestamp() {return new Date().getTime();}/*** 封装同步请求* @param paramMap* @param url* @return*/public static JSONObject sendRequest(Map<String, Object> paramMap, String url){String result = "";try {//封装post参数StringBuilder postdata = new StringBuilder();for (Map.Entry<String, Object> param : paramMap.entrySet()) {postdata.append(param.getKey()).append("=").append(param.getValue()).append("&");}log.info(String.format("--> 发送请求:post data %1s", postdata));byte[] reqData = postdata.toString().getBytes("utf-8");byte[] respdata = HttpUtil.doPost(url,reqData);result = new String(respdata);log.info(String.format("--> 应答结果:result data %1s", result));} catch (Exception ex) {ex.printStackTrace();}return JSONObject.parseObject(result);}
}

mogdodbp配置

#mongo 配置
spring.data.mongodb.uri=mongodb://yourhost:27017/test

创建接口 repository

//实体类,主键类型
@Repository
public interface HospitalRepository extends MongoRepository<Hospital,String> {
}

ApiController

//工具自带跨域解决
@Api(tags = "医院管理API接口")
@RestController
@RequestMapping("/api/hosp")  //请求地址参考文档;要与医院模拟系统那个一致
public class ApiController {@Autowiredprivate HospitalService hospitalService;}

上传医院功能实现

controller层

    @ApiOperation(value = "上传医院")@PostMapping("saveHospital")public Result saveHospital(HttpServletRequest request){//1.获取参数,转换类型Map<String, String[]> parameterMap = request.getParameterMap();Map<String, Object> paramMap = HttpRequestHelper.switchMap(parameterMap);//2. TODO 校验签名//3. 调用接口,数据入库hospitalService.saveHospital(paramMap);return Result.ok();}

serviceImpl层

    //上传医院@Overridepublic void saveHospital(Map<String, Object> parameterMap) {//1.转化参数类型paramMap => Hospital//String paramJsonString = JSONObject.toJSONString(parameterMap);//Hospital hospital = JSONObject.parseObject(paramJsonString, Hospital.class);String paramJsonString = JSONObject.toJSONString(parameterMap);Hospital hospital = JSONObject.parseObject(paramJsonString,Hospital.class);//2.根据hoscode查询医院信息Hospital targetHospital = hospitalRepository.getByHoscode(hospital.getHoscode());//3.存在医院信息进行更新if(targetHospital!=null){hospital.setId(targetHospital.getId());hospital.setCreateTime(targetHospital.getCreateTime());hospital.setUpdateTime(new Date());hospital.setStatus(targetHospital.getStatus());hospital.setIsDeleted(0);hospitalRepository.save(hospital);}else {//4.没有医院信息新增hospital.setCreateTime(new Date());hospital.setUpdateTime(new Date());hospital.setStatus(0);hospital.setIsDeleted(0);hospitalRepository.save(hospital);}}

签名校验

确认签名 (尚医通、医院方),要求签名一致

修改医院系统签名加密方式为MD5

实现医院签名接口方法

三.查询医院

四.科室接口–上传科室

五.查询科室

六.删除科室

七.排班的上传和查询接口

八.排班的分页查询和删除

nextday

一.集成nacos

二.医院列表

三.字典查询

四.医院上线状态

后端接口

前端实现

五.医院详情查询

后端接口

前端实现

nextday

一.Gateway集成


(1)科室信息(大科室与小科室树形展示)
(2)根据医院、科室按排班日期统计号源信息、按日期分页、根据日期判断周几
(3)根据医院、科室、排班日期查询排班列表数据

二.医院排班管理–科室列表

后端接口

前端实现

三.科室排班日期统计

后端接口

前端实现

四.排版详情列表

后端接口

前端实现

与本文相关的文章

发布评论

评论列表 (0)

  1. 暂无评论