尚医通项目(上)

2023-11-13

来自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_parent
	common
		common_util
		service_util
	model
	service
		service_hosp
		service_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 {

    @Bean
    public Docket webApiConfig(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                //只显示api路径下的页面,包含/api才显示
                .paths(Predicates.and(PathSelectors.regex("/api/.*")))
                .build();
    }

    @Bean
    public 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", "https://blog.csdn.net/m0_47498874?type=blog", "839623440@qq.com"))
                .build();
    }

    private ApiInfo adminApiInfo(){
        return new ApiInfoBuilder()
                .title("后台管理系统-API文档")
                .description("本文档描述了后台管理系统微服务接口定义")
                .version("1.0")
                .contact(new Contact("xiaoxin", "https://blog.csdn.net/m0_47498874?type=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.FAIL
    public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {
        Result<T> result = build(body);
        result.setCode(resultCodeEnum.getCode());
        result.setMessage(resultCodeEnum.getMessage());
        return result;
    }
//结果只返回状态码和信息,数据为空
    public static <T> Result<T> build(Integer code, String message) {
        Result<T> result = build(null);
        result.setCode(code);
        result.setMessage(message);
        return result;
    }
//成功的返回,只返回空数据及成功的状态
    public static<T> Result<T> ok(){
        return Result.ok(null);
    }

    /**
     * 操作成功
     * @param data
     * @param <T>
     * @return
     */
     //成功的返回,也包括数据
    public static<T> Result<T> ok(T data){
        Result<T> result = build(data);
        return build(data, ResultCodeEnum.SUCCESS);
    }
//失败的返回,只返回信息、状态码
    public static<T> Result<T> fail(){
        return Result.fail(null);
    }

    /**
     * 操作失败
     * @param data
     * @param <T>
     * @return
     */
     //失败的返回,也返回数据
    public static<T> Result<T> fail(T data){
//        Result<T> result = build(data);
        return build(data, ResultCodeEnum.FAIL);
    }
	//可以链式调用(返回的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)
	@ResponseBody
	public Result error(Exception e){
		//e.printStackTrace();
		return Result.fail(e.getmessage());
	}

	@ExceptionHandler(YyghException.class)
	@ResponseBody
	public 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();
    }

    @Override
    public 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 {

	/**
	 * 分页插件
	 *
	 */
	@Bean
	public 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可以看[这篇文章](http://t.csdn.cn/9qbIk)
git clone https://github.com/PanJiaChen/vue-admin-template.git
//下载相关依赖
npm install
//运行
 npm run dev

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

npm i -g node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/

一.结构

vue-damin-temeplate
	bulid:构建相关
	config:全局配置
	src:源代码
		api:所有请求
		assets:主题 字体等静态资源
		components:全局公共组件
		icons:项目所有svg icons
		router:路由
		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':'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif'}
      if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
        commit('SET_ROLES', data.roles)
      } else {
        reject('getInfo: roles must be a non-null array !')
      }
      commit('SET_NAME', data.name)
      commit('SET_AVATAR', data.avatar)
      // return new Promise((resolve, reject) => {
      //   getInfo(state.token).then(response => {
      //     const data = response.data
      //     if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
      //       commit('SET_ROLES', data.roles)
      //     } else {
      //       reject('getInfo: roles must be a non-null array !')
      //     }
      //     commit('SET_NAME', data.name)
      //     commit('SET_AVATAR', data.avatar)
      //     resolve(response)
      //   }).catch(error => {
      //     reject(error)
      //   })
      // })
    },

    // 登出
    LogOut({ commit, state }) {
      commit('SET_TOKEN', '')
      commit('SET_ROLES', [])
      removeToken()
      // return new Promise((resolve, reject) => {
      //   logout(state.token).then(() => {
      //     commit('SET_TOKEN', '')
      //     commit('SET_ROLES', [])
      //     removeToken()
      //     resolve()
      //   }).catch(error => {
      //     reject(error)
      //   })
      // })
    },

    // 前端 登出
    FedLogOut({ commit }) {
      // 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-table
      v-loading="listLoading"
      :data="list"
      element-loading-text="数据加载中"
      border
      fit
      highlight-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-button
            type="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.total
          this.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-button
            type="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对象是否存在id
            this.saveBtnDisabled = true
            if (!this.hospset.id) {
                //新增
                this.saveHospset()
            } else {
                //编辑
                this.updateHospset()
            }
 
        },
        //修改
        updateHospset(){
            hospsetApi.update(this.hospset).then(response=>{
                this.$message({
                    type: "success",
                    message: "修改成功!",
                });
                //页面跳转,用router
                this.$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-table
            v-loading="listLoading"
            :data="list"
            element-loading-text="数据加载中"
            border
            fit
            highlight-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.创建参数对象idList
                    let idList = [];
                    //2.遍历multipleSelection对象,取出id
                    for (let i = 0; i < this.multipleSelection.length; i++) {
                        let hospset = this.multipleSelection[i];
                        let id = hospset.id;
                        //3.存入idList
                        idList.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 {
 
    @Autowired
    private 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 DictServiceImpl
        extends ServiceImpl<DictMapper, Dict>
        implements DictService {
 
    //根据数据id查询子数据列表
    @Override
    public 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为1
        this.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>
    <!-- https://mvnrepository.com/artifact/com.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内容,从第二行开始
    @Override
    public void invoke(Stu stu, AnalysisContext analysisContext) {
        System.out.println("stu = " + stu);
    }
    //读取excel表头信息
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        System.out.println("表头信息:"+headMap);
    }
 
    //读取完成后执行
    @Override
    public 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>
    <!-- https://mvnrepository.com/artifact/com.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

	//导出数据字典接口

	@Override
	public 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-->DictVo
		List<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> {
 
    @Autowired
    private DictMapper dictMapper;
 
    @Override
    public void invoke(DictEeVo dictEeVo, AnalysisContext analysisContext) {
        //转换类型
        Dict dict = new Dict();
        BeanUtils.copyProperties(dictEeVo,dict);
        //设置默认值
        dict.setIsDeleted(0);
        dictMapper.insert(dict);
    }
 
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
 
    }
}

service层

    @Autowired
    DictListener dictListener;
 
    //导入字典数据
    @Override
    public 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
     */
    @Bean
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public 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
     */
    @Bean
    public 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和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
 
        //序列号key value
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
 
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
 
    /**
     * 设置CacheManager缓存规则
     * @param factory
     * @return
     */
    @Bean
    public 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=1800000
 
spring.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")
    @Override
    public List<Dict> findChildData(Long id) 
/**
     * 数据字典导入
     * @param file
     */
    @CacheEvict(value = "dict", allEntries = true)
    @Override
    public 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 {
 
    @Autowired
    private 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层

    //上传医院
    @Override
    public 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)根据医院、科室、排班日期查询排班列表数据

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

后端接口

前端实现

三.科室排班日期统计

后端接口

前端实现

四.排版详情列表

后端接口

前端实现

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

尚医通项目(上) 的相关文章

  • 具有 TINYTEXT 列的 CREATE TABLE 语句中出现语法错误 1064?

    这是我到目前为止的 MySQL 代码 CREATE DATABASE bankbase USE bankbase CREATE TABLE clienttable ClientID SMALLINT 15 NOT NULL DEFAULT
  • 错误:从上游读取响应标头时上游过早关闭连接 [uWSGI/Django/NGINX]

    我目前在用户正在进行的查询中总是得到 502 它通常返回 872 行 在 MySQL 中运行需要 2 07 然而 它返回了大量信息 每一行包含很多东西 有任何想法吗 运行 Django tastypie Rest API Nginx 和 u
  • SSL 连接在 MySQL Workbench 中有效,但在 DBeaver 中无效

    为当今读者编辑 这是旧版本 DBeaver 中的一个错误 随后已修复 我尝试在 DBeaver 中使用 SSL 连接到 Google Cloud SQL MySQL 实例时遇到 访问被拒绝 错误 我能够 在 MySQL Workbench
  • 如何在Redis中进行持久化存储?

    关闭redis服务器后 使用set存储的值被破坏 在这里我找到了使用持久性存储的方法 有人帮助我 如何使用javascript实现这一点 我想将客户端的一些值存储在 redis 数据库中 并且必须在其他客户端中使用该值 您需要配置 Redi
  • 如何正确转义 mysql“搜索/喜欢”查询?

    Summary 我目前正在使用 search field LIKE this gt db gt escape like str search string 逃避动态创建的搜索查询 创建的 SQL 语句结果不会产生任何错误 也不会产生任何结果
  • 如何将 PHP 会话数据保存到数据库而不是文件系统中?

    我有两个网站 一个是 TLS 一个不是 两个都适用于同一个客户端 但我需要这些网站彼此 并且仅彼此 共享通用数据users orders accounts etc 这通常可以通过以下方式完成 SESSION数据 但我显然这些不能跨其他站点工
  • uwsgi + Django REST框架:空闲时间后很少有缓慢的请求

    我正在运行 Django REST 框架 白天每分钟的请求率相当低 我注意到一个我无法解释或重现的问题 每天 在夜间或清晨 当我的 RPM 接近于零时 我会收到 1 10 个超慢的请求 我的平均响应时间100 到 200 毫秒之间 但是这个
  • PHP 从表行中检索数据并将其存储到变量

    我想这些问题已经说明了一切 我的查询结果会生成与条件匹配的行 我想从每个表列中获取每个数据并将其放入一个变量中 getinfo select user firstname user middlename user lastname from
  • 如何在 MySQL 查询编辑器中对列重新排序?

    我想移动专栏OtherSupport below Amount2 是否有捷径可寻 ALTER TABLE myTable MODIFY OtherSupport VARCHAR 50 AFTER Amount2
  • Lua中按字符分割字符串

    我有像这样的字符串 ABC DEF 我需要将它们分开 字符并将两个部分分别分配给一个变量 在 Ruby 中 我会这样做 a b ABC DEF split 显然Lua没有这么简单的方法 经过一番挖掘后 我找不到一种简短的方法来实现我所追求的
  • 如何在codeigniter中引用数据库连接?

    如何在 CodeIgniter 数据库处理程序对象中手动调用 PHP 数据库函数 如何检索连接 dbc 或者调用类似的函数mysql real escape string dbc variable 您可以调用任何 mysql 本机函数并访问
  • 如何向 MySQL 中的 ENUM 类型列添加更多成员?

    MySQL 参考手册没有提供关于如何执行此操作的明确示例 我有一个 ENUM 类型的国家 地区名称列 我需要向其中添加更多国家 地区 实现此目的的正确 MySQL 语法是什么 这是我的尝试 ALTER TABLE carmake CHANG
  • SQL选择符号||是什么意思意思是?

    什么是 在 SQL 中做什么 SELECT a b AS letter 表示字符串连接 不幸的是 字符串连接不能在所有 sql 方言之间完全移植 ANSI SQL 中缀运算符 mysql concat 可变参数函数 caution 表示 逻
  • Laravel 5.5 中的主从配置

    如何配置 Laravel 5 5 主从 MySQL 复制 我想分别在master和slave上进行写操作和读操作 可选 有没有办法在理想条件下进行连接池和打开连接的最大 最小数量 只需改变你的config database php文件包含读
  • 为什么 Redis TimeSeries 不捕获聚合中的最后一个元素?

    我试图了解 Redis 的时间序列规则创建的工作原理 但我很困惑为什么 Redis 会忽略聚合中的最后一项 并想知道这是否是预期的行为 我在中创建了示例代码redis cli为了显示 127 0 0 1 6379 gt FLUSHALL O
  • MySQL中Join同表临时表

    我喜欢在 MySQL 中加入一个失败的临时表 这个想法很简单 CREATE TEMPORARY TABLE temp table LIKE any other table srsly it does not matter which tab
  • Laravel 雄辩的 withCount() 应该比 with() 慢

    所以我问这个的原因是在我当前的应用程序中withCount 与仅通过以下方式获取关系的所有数据相比 响应时间几乎增加了三倍with 并只是从前端获取长度 javascript 我认为使用的要点withCount 是为了加快查询速度 但也许我
  • MySQL 连接逗号分隔字段

    我有两张桌子 第一个表是batch在字段 batch 中包含逗号分隔的学生 ID 的表 batch id batch 1 1 2 2 3 4 第二个表是分数 marks id studentid subject marks 1 1 Engl
  • PHP strtotime返回Mysql UNIX_TIMESTAMP的不同值

    我在 stackoverflow 上搜索过帖子 发现了一些类似的帖子 但我认为这是一篇不同的帖子 我的 PHP 和 Mysql 服务器的时区全部设置为 UTC 在表中我使用时间戳字段 值为 2010 11 08 02 54 15 我使用这样
  • SQL查询:按字符长度排序?

    是否可以按字符总数对sql数据行进行排序 e g SELECT FROM database ORDER BY data length 我想你想用这个 http dev mysql com doc refman 5 0 en string f

随机推荐

  • 手把手教你上手Apache DolphinScheduler机器学习工作流

    摘要 Apache DolphinScheduler 3 1 0发版后 添加了诸多AI组件 帮助用户在Apache DolphinScheduler上更方便地构建机器学习工作流 本文介绍如何建立DolphinScheduler与一些机器学习
  • Windows中如何查看日志(如查看远程登陆的IP地址)以及常用日志ID

    时间 2018 12 12 题目 Windows中如何查看日志 如查看远程登陆的IP地址 以及常用日志ID 概述 在Windows中可以使用 事件查看器 来查看相关日志 并结合日志ID进行日志筛选 常见的日志有 4634 帐户被注销 464
  • 将SSE指令转换为ARM NEON指令

    相关资料 sse指令集 sse指令解释 sse2neon仓库 可以在sse2neon h中寻找对应的neon指令转换方法 注意事项 将sse指令转换为arm neon指令往往很难起到优化作用 甚至可能产生负优化 因此该部分优化仅供参考 mm
  • 12.计算机网络---iptables防火墙管理工具

    文章目录 一 防火墙基础知识 1 1 防火墙是什么 1 2 iptables基础知识 1 3 netfilter和iptables的关系 1 4 新型防火墙工具 firewalld 二 iptables的四表五链 2 1 规则表 2 2 规
  • Python爬虫之使用MongoDB存储数据

    1 MongoDB的安装 MongoDB是一种非关系型数据库 MongoDB官网 选择你的系统对应的版本下载安装即可 2 MongoDB配置 a 在C盘或者D盘建一个文件夹如图mongodb b 安装成功后里面会有bin文件然后再文件夹里面
  • sudo配置文件/etc/sudoers深入介绍

    简介 sudo命令对应的用户权限授权配置文件为 etc sudoers 我们可以使用专用工具visudo来完成有关sudo的授权管理配置 使用visudo工具的好处是在添加规则之后 保存退出时会检查授权配置的语法 这一点很重要 曾经有人直接
  • 理解矩阵 from孟岩--流星小屋

    理解矩阵 from孟岩 前不久chensh出于不可告人的目的 要充当老师 教别人线性代数 于是我被揪住就线性代数中一些务虚性的问题与他讨论了几次 很明显 chensh觉得 要让自己在讲线性代数的时候不被那位强势的学生认为是神经病 还是比较难
  • vscode 用git 拉取代码,提示:在签出前,请清理存储库工作树。请问是什么问题,如何解决

    问题主要是git仓库上的代码和本地代码存在冲突 解决办法 1 新建一个文件夹重新从git拉取最新的代码 使用beyond compare对比合并自己修改的代码到新拉的代码里 提交 2 放弃本地修改 直接覆盖 git reset hard g
  • Hadoop分布式文件系统(HDFS)Java接口(HDFS Java API)详细版

    误用聪明 何若一生守拙 滥交朋友 不如终日读书 相关连接 HDFS相关知识 Hadoop分布式文件系统 HDFS 快速入门 Hadoop分布式文件系统 HDFS 知识梳理 超详细 Hadoop集群连接 Eclipse连接Hadoop集群 I
  • Vant UI使用iconfont自定义图标

    在使用Vant UI做h5页面时 不可避免的会使用到各种小图标 但是Vant 官方提供的图标是有限的 考虑到这种情况 vant也提供了一种方法去自定义图标 自定义图标 可能有些同学看到这里也是一头雾水 下面有详细的教程 iconfont 让
  • 栈的逆序

    题目描述 实现一个栈的逆序 但是只能用递归函数和这个栈本身的pop操作来实现 而不能自己申请另外的数据结构 给定一个整数数组A即为给定的栈 同时给定它的大小n 请返回逆序后的栈 测试用例 输入 4 3 2 1 4 输出 1 2 3 4 解题
  • 伯德图 matlab,Matlab/Simulink中bode图的画法

    在Matlab中 大多时候 我们都是用M语言 输入系统的传递函数后 用bode函数绘制bode图对系统进行频率分析 这样做 本人觉得效率远不如Simulink建模高 如何在Matlab Simulink中画bode图 以前也在网上查过些资料
  • 《数据库系统概论(第5版)》课后习题答案 王珊、萨师煊编著版 课后题解析 高等教育出版社出版 答案与解析第二篇 第1章 课后答案与解析

    数据库系统概论 第5版 课后答案 数据库系统概论 第5版 课后习答案 王珊 萨师煊编著版 课后题解析 高等教育出版社出版 答案与解析 数据库系统概论 第5版 王珊 萨师煊编著版 第二篇 第1章 课后答案与解析 完整答案在页面最下方 前言 第
  • unity3D期末作业 开车游戏

    unity3D期末作业 开车游戏 文末附下载链接 游戏如下动态图 点我下载 https download csdn net download weixin 43474701 75857348
  • 代码运行时 CPU占用率100%的解决方法

    原因 建立连接后启动新的线程 如果线程中有简单粗暴的不含阻塞的while 1 循环 会持续占用CPU 导致CPU占用率极高 解决 在while 1 的大循环中插入一句sleep 1 即阻塞1毫秒 java线程内则使用Thread sleep
  • LeetCode【541】反转字符串 II

    题目 给定一个字符串和一个整数 k 你需要对从字符串开头算起的每个 2k 个字符的前k个字符进行反转 如果剩余少于 k 个字符 则将剩余的所有全部反转 如果有小于 2k 但大于或等于 k 个字符 则反转前 k 个字符 并将剩余的字符保持原样
  • 数据集划分,Oxford Flower102花卉分类数据集,分为训练集、测试集、验证集

    数据集划分 Oxford Flower102花卉分类数据集 分为训练集 测试集 Oxford Flower102数据集链接 https www robots ox ac uk vgg data flowers 102 参考 https ww
  • R语言练习题答案(1)

    关注公众号凡花花的小窝 含有更多更全面的计算机专业编程考研相关知识的文章还有资料 第一章R语言概述 代码 1 1 install packages installr require installr load install load in
  • Midjourney 绘画关键词12000+,直接复制粘贴,让你轻松掌握AI绘画技巧!

    今天我要跟大家介绍一款非常实用的AI绘画工具 Midjourney 如果你是一名画家或设计师 你一定知道 画画是一件非常需要耐心和技巧的事情 但是有了Midjourney 你可以省去繁琐的绘制过程 快速创作出精美的作品 Midjourney
  • 尚医通项目(上)

    来自atguigu 视频链接 项目介绍 1 概述 尚医通即为网上预约挂号系统 旨在缓解看病难 挂号难的就医难题 随时随地轻松挂号 不用排长队 2 技术点 核心技术 SpringBoot 简化新Spring应用的初始搭建以及开发过程 Spri