> 文档中心 > 尚医通项目--功能模块集成

尚医通项目--功能模块集成


一、Mybatis-Plus

使用Mybatis-Plus的项目模块:
 Service包:
      service-hosp医院服务接口模块

Mybatis-Plus的配置、集成以及CRUD操作请看我的另一篇博客
https://blog.csdn.net/tufhgddty/article/details/123332559?spm=1001.2014.3001.5501

二、Swagger2

引入依赖

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

添加配置类

/ * Swagger2配置信息 */@Configuration@EnableSwagger2public class Swagger2Config {    @Bean    public Docket webApiConfig(){ return new Docket(DocumentationType.SWAGGER_2)  .groupName("webApi")  .apiInfo(webApiInfo())  .select()  //只显示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("atguigu", "http://atguigu.com", "493211102@qq.com"))  .build();    }    private ApiInfo adminApiInfo(){ return new ApiInfoBuilder()  .title("后台管理系统-API文档")  .description("本文档描述了后台管理系统微服务接口定义")  .version("1.0")  .contact(new Contact("atguigu", "http://atguigu.com", "49321112@qq.com"))  .build();    }}

1.默认的访问地址
http://localhost:端口/swagger-ui.html;

2.如果配置文件中配置了 context-path:/目录
http://localhost:端口/目录/swagger-ui.html
在这里插入图片描述

swagger通过注解表明该接口会生成文档,包括接口名、请求方法、参数、返回信息的等等。
@Api:修饰整个类,描述Controller的作用
@ApiOperation:描述一个类的一个方法,或者说一个接口
@ApiParam:单个参数描述
@ApiModel:用对象来接收参数
@ApiModelProperty:用对象接收参数时,描述对象的一个字段
@ApiImplicitParam:一个请求参数 @ApiImplicitParams:多个请求参数

三、自定义全局异常处理

spring boot 默认情况下会映射到 /error 进行异常处理,但是提示并不十分友好,下面自定义异常处理,提供友好展示。
自定义全局异常类
我们在搭建模块时在common-util模块已经添加了YyghException类,这里不做解释

添加全局异常处理类
在common/common_util包里的exception文件夹下添加

@Data@ToString@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();    }
package com.atguigu.yygh.common.exception;/ * 全局异常处理类 * */@ControllerAdvice@ControllerAdvice来声明一些全局性的东西,最常见的是结合@ExceptionHandler注解用于全局异常的处理】public class GlobalExceptionHandler {@ExceptionHandler(Exception.class) 【异常处理Handler注解,参数为异常类型,即出现哪种异常用这个异常处理Hanlder方法】@ResponseBody 【让返回的结果可以输出(以Json格式)】public Result error(Exception e){ e.printStackTrace(); 【输出异常内容】return Result.fail(); 【前面集成的,自定义返回结果】    }/     * 自定义异常处理方法     * @param e* @return*/@ExceptionHandler(YyghException.class)@ResponseBodypublic Result error(YyghException e){return Result.build(e.getCode(), e.getMessage()); 【根据传入的CodeMessage实现返回自定义异常结果】    }}

自定义异常处理方法需要手动抛出异常,并且传入自定义的参数:错误信息Message和错误状态码Code
尚医通项目--功能模块集成

四、LogBack

配置日志级别
日志记录器(Logger)的行为是分等级的。如下表所示:
分为:OFF关闭、FATAL致命、ERROR错误、WARN警告、INFO消息、DEBUG调试、ALL所有
默认情况下,spring boot从控制台打印出来的日志级别只有INFO及以上级别,可以配置日志级别

在application.properties设置日志级别

logging.level.root=WARN

这种方式只能将日志打印在控制台上
LogBack日志
spring boot内部使用Logback作为日志实现的框架。
Logback和log4j非常相似,如果你对log4j很熟悉,那对logback很快就会得心应手。
配置 resources/logback-spring.xml(能看懂就行,不要求掌握

<configuration  scan="true" scanPeriod="10 seconds">    <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR                 <contextName>logback</contextName>        <property name="log.path" value="D:/yygh_log/edu" />                                <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">   <filter class="ch.qos.logback.classic.filter.ThresholdFilter">     <level>INFO</level> </filter> <encoder>     <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>          <charset>UTF-8</charset> </encoder>    </appender>            <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>  <filter class="ch.qos.logback.classic.filter.LevelFilter">     <level>INFO</level>     <onMatch>ACCEPT</onMatch>     <onMismatch>DENY</onMismatch> </filter>    </appender>        <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>  <filter class="ch.qos.logback.classic.filter.LevelFilter">     <level>warn</level>     <onMatch>ACCEPT</onMatch>     <onMismatch>DENY</onMismatch> </filter>    </appender>        <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>  <filter class="ch.qos.logback.classic.filter.LevelFilter">     <level>ERROR</level>     <onMatch>ACCEPT</onMatch>     <onMismatch>DENY</onMismatch> </filter>    </appender>    <!-- 用来设置某一个包或者具体的某一个类的日志打印级别、以及指定。 仅有一个name属性, 一个可选的level和一个可选的addtivity属性。 name:用来指定受此logger约束的某一个包或者具体的某一个类。 level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,如果未设置此属性,那么当前logger将会继承上级的级别。    -->    <!-- 使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作: 第一种把改成这样就会打印sql,不过这样日志那边会出现很多其他消息 第二种就是单独给mapper下目录配置DEBUG模式,代码如下,这样配置sql语句会打印,其他还是正常DEBUG级别:     -->        <springProfile name="dev">  <logger name="com.guli" level="INFO" />  <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>

五、EasyExcel

EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel
官方文档https://www.yuque.com/easyexcel/doc/easyexcel
添加依赖,在service-cmn接口模块

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

添加依赖,在model模块,因为需要在实体类加上EasyExcel的注解
【注意此处加了provided】对标签的描述点这里

<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><scope>provided </scope>    </dependency>

添加依赖,在common-util模块,因为后面要把导入导出封装为所有模块公用的工具类

<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId></dependency>

实体类,在model模块的com.atguigu.yygh.vo.cmn.DictEeVo

package com.atguigu.yygh.vo.cmn;@Datapublic class DictEeVo {@ExcelProperty(value = "id",index = 0) private Long id;@ExcelProperty(value = "上级id",index = 1)private Long parentId;@ExcelProperty(value = "名称",index = 2)private String name;@ExcelProperty(value = "值",index = 3)private String value;@ExcelProperty(value = "编码",index = 4)private String dictCode;}

接口方法,在DictService

/ * 导出 * @param response*/void exportData(HttpServletResponse response);

实现类方法,在DictServiceImpl

@Overridepublic void exportData(HttpServletResponse response) {try {      response.setContentType("application/vnd.ms-excel");      response.setCharacterEncoding("utf-8");// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系String fileName = URLEncoder.encode("数据字典", "UTF-8");      response.setHeader("Content-disposition", "attachment;filename="+ fileName + ".xlsx");      List<Dict> dictList = dictMapper.selectList(null);      List<DictEeVo> dictVoList = new ArrayList<>(dictList.size());for(Dict dict : dictList) {  DictEeVo dictVo = new DictEeVo();  BeanUtils.copyBean(dict, dictVo, DictEeVo.class);  dictVoList.add(dictVo);      }      EasyExcel.write(response.getOutputStream(), DictEeVo.class).sheet("数据字典").doWrite(dictVoList);   } catch (IOException e) {      e.printStackTrace();   }}

Controller类方法,在DictController

@ApiOperation(value="导出")@GetMapping(value = "/exportData")public void exportData(HttpServletResponse response) {dictService.exportData(response);}

六、Spring Cache

因为缓存也是公共使用,所有的service模块都有可能使用缓存,所以我们把依赖与部分配置加在service-util模块,这样其他service模块都可以使用了
添加依赖
service-util模块的pom.xml

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

service-util添加配置类
创建com.atguigu.yygh.common.config.RedisConfig

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

service-cmn添加redis配置
application.properties

spring.redis.host=192.168.44.165spring.redis.port=6379spring.redis.database= 0spring.redis.timeout=1800000spring.redis.lettuce.pool.max-active=20spring.redis.lettuce.pool.max-wait=-1#最大阻塞等待时间(负数表示没限制)spring.redis.lettuce.pool.max-idle=5spring.redis.lettuce.pool.min-idle=0

使用Spring Cache

@Cacheable
根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。

属性/方法名 解释
value 缓存名,必填,它指定了你的缓存存放在哪块命名空间
cacheNames 与 value 差不多,二选一即可
key 可选属性,可以使用 SpEL 标签自定义缓存的key

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

@CacheEvict
使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上
参数比@Cacheable多两个

属性/方法名 解释
allEntries 是否清空所有缓存,默认为 false。如果指定为true,则方法调用后将立即清空所有的缓存
beforeInvocation 是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存

应用:改造com.atguigu.yygh.cmn.service.impl.DictServiceImpl类方法

/ * 根据上级id获取子节点数据列表 * @param parentId*/@Cacheable(value = "dict",keyGenerator = "keyGenerator")@Overridepublic List<Dict> findByParentId(Long parentId) {List<Dict> dictList = dictMapper.selectList(new QueryWrapper<Dict>().eq("parent_id", parentId));dictList.stream().forEach(dict -> {boolean isHasChildren = this.isHasChildren(dict.getId());    dict.setHasChildren(isHasChildren);});return dictList;}/ * 导入 * allEntries = true: 方法调用后清空所有缓存 * @param file*/@CacheEvict(value = "dict", allEntries=true)@Overridepublic void importData(MultipartFile file) {   ExcelHelper fileHelper = new ExcelHelper(DictEeVo.class);   List<DictEeVo> dictVoList = fileHelper.importExcel(file);if(!CollectionUtils.isEmpty(dictVoList)) {dictMapper.insertBatch(dictVoList);   }}

七、Redis

即将根据项目写一篇专门的博客

八、Nginx

下载安装nginx(window版)

在nginx.conf里的http{ }中添加

server { listen9001; server_name  localhost;location ~ /hosp/ { proxy_pass http://localhost:8201;}location ~ /cmn/ { proxy_pass http://localhost:8202;}}

前端 调整/config/dev.env.js中的BASE_API

BASE_API: 'http://localhost:9001'

九、MongoDB

MongoDB的CRUD操作请看另一篇博客
https://blog.csdn.net/tufhgddty/article/details/123540812

十、图片Bash64编码工具类

十一、SpringCloud Nacos

十二、SpringCloud Feign

十三、Spring Cloud Gateway

十四、JWT

十五、阿里云短信服务

十六、用户认证与网关整合

十七、OAuth2 微信登录

十八、阿里云OSS对象存储服务

十九、RabbitMQ

暂时有一篇博客
https://blog.csdn.net/tufhgddty/article/details/120073169?spm=1001.2014.3001.5501
即将根据项目写一篇专门的博客

二十、Docker