功能模块:尚医通(预约挂号平台、管理平台、接口系统)、医院系统(接口系统)
业务流程:
架构设计需要考虑的几个方面:
微服务架构
,引入消息中间件
。分布式部署
,引入缓存
。账号密码管理
。另外涉及到身份证照片的上传,需要有加密和权限控制功能的文件服务器
。系统涉及第三方微信登录和微信支付,引入微信登录接口和支付接口
,确保登录和支付的安全。负载均衡
,甚至是异地多活
这类复杂的方案。如果数据丢失,修复将会非常麻烦,只能靠人工逐条修复,这个很难接受,因此需要考虑存储高可靠
。我们需要考虑多种异常情况:机器故障、机房故障,针对机器故障,我们需要设计 MySQL 同机房主备
方案;针对机房故障,我们需要设计 MySQL 跨机房同步
方案。为了减轻MySQL服务器的访问压力,我们引入更灵活的MongoDB
数据库。云服务器和云服务
。系统中我们是用了阿里云OSS和阿里云市场短信。整个项目基于一个已经开发好的“通用权限系统”
进行二次开发,因此我们先将“通用权限系统”搭建并运行起来
资料:资料>权限系统>guigu_common_system.sql
mkdir -p /atguigu/syt/redis/{data,conf}
在 /atguigu/syt/redis/conf
目录中创建文件 redis.conf
,文件从
资料:资料>权限系统>redis-6.2.7.tar.gz
中解压获取
修改默认配置(从上至下依次):
#bind 127.0.0.1 #注释掉这部分,这是限制redis只能本地访问
protected-mode no #默认yes,开启保护模式,限制为本地访问
daemonize no #默认no,改为yes意为以守护进程方式启动,可后台运行,#注意!!!此处是no,否则redis无法通过配置文件启动
也可以直接从资料目录复制,这里是已经修改好的配置文件
资料:资料>权限系统>redis.conf
docker run -d --restart=always --name atguigu_syt_redis \
-p 6379:6379 \
-v /atguigu/syt/redis/conf/redis.conf:/redis.conf \
-v /atguigu/syt/redis/data:/data \
redis:6.2.7 \
redis-server /redis.conf \
--appendonly yes --requirepass "123456"
参数解释:
-d 后台运行
-p 端口映射到主机的端口
-v 将主机目录挂载到容器的目录
redis-server --appendonly yes : 在容器执行redis-server启动命令,并打开appendonly持久化配置
–requirepass :设置密码
常见问题:以下IPv4问题会导致无法远程链接redis服务
解决方案:
#修改配置文件:
vim /usr/lib/sysctl.d/00-system.conf
#追加
net.ipv4.ip_forward=1
#接着重启网络
systemctl restart network
docker exec -it atguigu_syt_redis redis-cli
auth 123456
set name "xiaogu"
在本机使用客户端工具远程链接redis,测试是否能成功连接
资料:资料>权限系统>guigu-syt-parent.zip
将压缩包解压,并放在自己的工作目录下,启动前注意检查:
配置文件:修改application-dev.yml中MySQL和redis的相关参数
idea的Maven配置:配置到自己的Maven配置文件和本地仓库目录中
idea中配置的jdk版本:1.8.0_300以上(微信支付安全策略要求)
idea的项目编码:utf8
MySQL版本:8.0
访问Swagger测试页面:http://localhost:8800/doc.html
执行登录测试,并获取token:用户名是admin
,密码是111111
资料:资料>权限系统>guigu-syt-ui.zip
将压缩包解压,并放在自己的工作目录下,启动前注意检查:
执行以下命令启动项目
npm i #安装项目依赖模块
npm run dev #运行程序
访问前端页面:http://localhost:9528/
|-guigu-syt-parent:项目父节点,管理多个子模块
|-common:公共模块父节点
|-common-log:系统日志
|-common-util:通用工具类
|-model:实体类
|-service-util:微服务通用配置
|-spring-security:用户认证和授权
|-service:微服务父节点
|-service-system:认证和授权管理微服务
说明:model
模块中的com.atguigu.syt
是尚医通项目中的实体类
这里列出重要的目录和文件:
|-node_modules 下载的模块依赖
|-public
|-index.html 入口页面
|-src
|-api 后端api请求模块
|-assets 公共资源文件
|-components 通用组件
|-layout 页面布局
|-router 路由配置
|-utils 通用工具
|-request.js axios封装
|-views 页面组件
|-dashboard 首页面
|-login 登录页面
|-system 通用权限页面
|-App.vue 根组件
|-main.js 入口js
|-settings.js 应用基本配置
|-package.json 当前项目基本信息和依赖信息
|-vue.config.js webpack相关配置
当医院入驻尚医通平台后,尚医通管理员会为每个医院创建一条医院设置记录,这里会为每个医院分配一个唯一的医院编码,分配一个用于远程接口调用的密钥,并配置每个医院的远程接口主机地址,以及医院信息系统的联系人、联系方式等相关信息。只有平台为医院添加了医院设置信息,医院才可以上传医院基本信息、医院预约规则信息、医院科室信息、医院排班信息等内容到平台中。
资料:资料>医院设置微服务>guigu_syt_hosp.sql
数据库:guigu_syt_hosp
表结构:hospital_set
hosname:医院名称
hoscode:医院编号(平台分配,全局唯一,api接口必填信息)
api_url:医院接口调用的主机地址(如:预约下单,我们要调用该地址去医院下单)
sign_key:双方api接口调用的签名key,由平台生成
contacts_name:医院联系人姓名
contacts_phone:医院联系人手机
status:状态(锁定/解锁)
在service下创建子模块,选择 maven类型的模块,输入模块名称service-hosp
,完成创建
创建后删除默认的主类Main.java
在service-hosp中添加依赖:
<dependencies>
<!--实体-->
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>model</artifactId>
<version>1.0</version>
</dependency>
<!--服务通用配置-->
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>service-util</artifactId>
<version>1.0</version>
</dependency>
<!--自定义安全模块-->
<dependency>
<groupId>com.atguigu</groupId>
<artifactId>spring-security</artifactId>
<version>1.0</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
在service-util中添加代码生成器:
<!--MP代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
</dependency>
<!--代码生成器模板-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>provided</scope>
</dependency>
在service-util的test目录中,创建包com.atguigu.syt
,
从资料中复制代码生成器
资料:资料>医院设置微服务>CodeGen.java
确认代码生成器中变量moduleName
的值为hosp
,以及数据源配置,然后运行程序
然后删除service-hosp微服务中的entity包,修改mapper层和service层引入的实体类,并引入model模块中的实体类
在service-hosp模块中resources目录下创建文件
application.yml
:
spring:
application:
name: service-hosp
profiles:
active: dev,redis
application-dev.yml
:
server:
port: 8201
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:com/atguigu/syt/hosp/mapper/xml/*.xml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
password: 123456
url: jdbc:mysql://localhost:3306/guigu_syt_hosp?characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false
username: root
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
在包com.atguigu.syt.hosp
中创建启动类ServiceHospApplication.java
package com.atguigu.syt.hosp;
@SpringBootApplication
@ComponentScan(basePackages = {"com.atguigu"})
public class ServiceHospApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceHospApplication.class, args);
}
}
简单的功能我们可以直接在controller中实现
注意:将@RequestMapping路径前面添加 /admin
前缀
package com.atguigu.syt.hosp.controller.admin;
@Api(tags = "医院设置管理")
@RestController
@RequestMapping("/admin/hosp/hospitalSet")
public class AdminHospitalSetController {
@Resource
private HospitalSetService hospitalSetService;
@ApiOperation(value = "根据id查询医院设置")
@GetMapping("/getHospSet/{id}")
public Result<HospitalSet> getById(
@ApiParam(value = "医院设置id",required = true)
@PathVariable Long id){
HospitalSet hospitalSet = hospitalSetService.getById(id);
return Result.ok(hospitalSet);
}
@ApiOperation(value = "根据id删除医院设置")
@DeleteMapping("/{id}")
public Result removeById(
@ApiParam(value = "医院设置id",required = true)
@PathVariable Long id){
boolean result = hospitalSetService.removeById(id);
if(result){
return Result.ok().message("删除成功");
}else{
return Result.fail().message("删除失败");
}
}
@ApiOperation(value = "新增医院设置")
@PostMapping("/saveHospSet")
public Result save(
@ApiParam(value = "医院设置对象", required = true)
@RequestBody HospitalSet hospitalSet){
//设置状态 1可用 0锁定
hospitalSet.setStatus(1);
//生成签名秘钥
Random random = new Random();
hospitalSet.setSignKey(MD5.encrypt(System.currentTimeMillis()+""+random.nextInt(1000)));
boolean result = hospitalSetService.save(hospitalSet);
if(result){
return Result.ok().message("添加成功");
}else{
return Result.fail().message("添加失败");
}
}
@ApiOperation(value = "根据ID修改医院设置")
@PutMapping("/updateHospSet")
public Result updateById(
@ApiParam(value = "医院设置对象", required = true)
@RequestBody HospitalSet hospitalSet){
boolean result = hospitalSetService.updateById(hospitalSet);
if(result){
return Result.ok().message("修改成功");
}else{
return Result.fail().message("修改失败");
}
}
@ApiOperation(value = "批量删除医院设置") //[1,2,3]
@DeleteMapping("/batchRemove")
public Result batchRemoveHospitalSet(
@ApiParam(value = "id列表", required = true)
@RequestBody List<Long> idList) {
boolean result = hospitalSetService.removeByIds(idList);
if(result){
return Result.ok().message("删除成功");
}else{
return Result.fail().message("删除失败");
}
}
@ApiOperation(value = "医院设置锁定和解锁")
@PutMapping("/lockHospitalSet/{id}/{status}")
public Result lockHospitalSet(
@ApiParam(value = "医院设置id",required = true) @PathVariable Long id,
@ApiParam(value = "状态", required = true) @PathVariable Integer status) {
if(status != 0 && status != 1){
return Result.fail().message("非法数据");
}
HospitalSet hospitalSet = new HospitalSet();
hospitalSet.setId(id);
hospitalSet.setStatus(status);
boolean result = hospitalSetService.updateById(hospitalSet);
if(result){
return Result.ok().message("操作成功");
}else{
return Result.fail().message("操作失败");
}
}
}
注意:先登录,获取token,然后设置全局token
复杂的功能我们需要添加service层
AdminHospitalSetController中添加分页方法
@ApiOperation("分页条件查询")
@GetMapping("/{page}/{limit}")
public Result<Page> pageList(
@ApiParam(value = "页码",required = true)
@PathVariable Long page,
@ApiParam(value = "每页记录数",required = true)
@PathVariable Long limit,
@ApiParam(value = "查询对象",required = false)
HospitalSetQueryVo hospitalSetQueryVo
){
Page<HospitalSet> pageParam = hospitalSetService.selectPage(page, limit, hospitalSetQueryVo);
return Result.ok(pageParam);
}
接口:HospitalSetService
/**
* 分页查询
* @param page 页码
* @param limit 每页记录数
* @param hospitalSetQueryVo 查询对象
* @return 分页对象
*/
Page<HospitalSet> selectPage(Long page, Long limit, HospitalSetQueryVo hospitalSetQueryVo);
实现:HospitalSetServiceImpl
@Override
public Page<HospitalSet> selectPage(Long page, Long limit, HospitalSetQueryVo hospitalSetQueryVo) {
//分页参数
Page<HospitalSet> pageParam = new Page<>(page, limit);
//查询参数
QueryWrapper<HospitalSet> queryWrapper = new QueryWrapper<>();
String hosname = hospitalSetQueryVo.getHosname();
queryWrapper.like(!StringUtils.isEmpty(hosname),"hosname", hosname);
String hoscode = hospitalSetQueryVo.getHoscode();
queryWrapper.eq(!StringUtils.isEmpty(hoscode), "hoscode", hoscode);
//执行查询
baseMapper.selectPage(pageParam, queryWrapper);
return pageParam;
}
spring boot内部使用Logback
作为日志实现的框架。
通过日志查看程序的运行过程,运行信息,异常信息等
统一日志处理的目的:
日志记录器(Logger)的行为是分等级的。如下表所示:
从高到低分为:ERROR、WARN、INFO、DEBUG、TRACE
可以在service-hosp的application-dev.yml文件中,通过以下配置设置日志级别
logging:
level:
root: info
启动service-hosp微服务,可以看到微服务的启动日志在控制台上输出了info级别的内容
也可以修改成debug、error等其他级别查看日志输出情况
在AdminHospitalSetController类上添加注解:
@Slf4j
添加测试方法:
@ApiOperation(value = "日志测试")
@GetMapping("/log")
public Result log(){
log.trace("getHospSet trace");
log.debug("getHospSet debug");
log.info("getHospSet info");
log.warn("getHospSet warn");
log.error("getHospSet error");
return Result.ok();
}
在application-dev.yml文件中修改日志级别,可以看到日志的输出情况。
可以创建独立的日志配置文件,配置更强大的日志功能
在service-util的resources目录 中创建 logback-spring.xml
(默认日志文件的名字)
在logback-spring.xml中配置如下内容:
<!--
scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!--每个logger都关联到logger上下文,默认上下文名称为“default”。
可以使用contextName标签设置成其他名字,用于区分不同应用程序的记录-->
<contextName>syt</contextName>
<!--
用来定义变量值的标签,property标签有两个属性,name和value;
其中name的值是变量的名称,value的值是变量定义的值。
通过property定义的值会被插入到logger上下文中。
定义变量后,可以使“${name}”来使用变量。
-->
<property name="charset" value="UTF-8"></property>
<!--控制台彩色日志日志格式-->
<!-- magenta:洋红 -->
<!-- boldMagenta:粗红-->
<!-- cyan:青色 -->
<!-- white:白色 -->
<!-- magenta:洋红 -->
<property name="CONSOLE_LOG_PATTERN"
value="%boldMagenta(%date{yyyy-MM-dd HH:mm:ss}) %highlight(%-5level) %thread %magenta(%file:%line) %green(%logger) - %msg%n"/>
<!-- 默认的控制台日志输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>${charset}</charset>
</encoder>
</appender>
<!--
根logger,且只有一个level属性,定义日志输出的默认级别
如果微服务中配置了logging:level:xxx,那么微服务中设置的级别优先
如果微服务中没有配置日志级别,那么日志按照此处配置的级别输出
-->
<root level="info">
<!--ref:引用appender的name的值-->
<appender-ref ref="STDOUT"/>
</root>
</configuration>
启动service-hosp,微服务的启动信息在控制台按照CONSOLE_LOG_PATTERN
中设置的格式和颜色输出,并且输出的级别由root节点的level属性
指定
控制台日志只在开发环境下使用,生产环境下我们不使用控制台日志,而是需要将日志输出到文件中。
在logback-spring.xml中配置如下内容:
<!--
scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!--每个logger都关联到logger上下文,默认上下文名称为“default”。
可以使用contextName标签设置成其他名字,用于区分不同应用程序的记录-->
<contextName>syt</contextName>
<!--
用来定义变量值的标签,property标签有两个属性,name和value;
其中name的值是变量的名称,value的值是变量定义的值。
通过property定义的值会被插入到logger上下文中。
定义变量后,可以使“${name}”来使用变量。
-->
<property name="charset" value="UTF-8"></property>
<!-- 属性文件:在properties文件中找到对应的配置项 -->
<property name="logging.position" value="D:/project/yygh/logs/"/>
<springProperty scope="context" name="logging.path" source="logging.file.path"/>
<springProperty scope="context" name="logging.level" source="logging.level.root"/>
<appender name="SYT-LOGGER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${logging.level}</level>
</filter>
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${logging.position}${logging.path}/syt-log.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>${charset}</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天日志归档路径以及格式 -->
<fileNamePattern>${logging.position}${logging.path}/syt-log-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>1MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
<!--
根logger,且只有一个level属性,定义日志输出的默认级别
如果微服务中配置了logging:level:xxx,那么微服务中设置的级别优先
如果微服务中没有配置日志级别,那么日志按照此处配置的级别输出
-->
<root level="info">
<appender-ref ref="SYT-LOGGER"/>
</root>
</configuration>
在service-hosp的application-dev.yml文件中,通过以下配置设置日志级别和日志路径
logging:
level:
root: info
file:
path: hosp
所以控制台中将只会打印出Spring Boot的bannar之后就啥也不打印了,所有的启动信息都会被打印在日志文件syt-log.log中。
但是实际上我们不希望业务日志
中包括这些启动信息
。所以这个时候我们就需要通过logger标签来搞事情了。将上面的配置文件进行简单修改:将标签改为标签
<!--
用来设置某一个包或者具体的某一个类的日志打印级别以及指定appender。
-->
<logger name="com.atguigu.syt" level="${logging.level}" additivity="false">
<appender-ref ref="SYT-LOGGER" />
</logger>
通过如下的方式设置节点,可以只打印一个级别的日志在指定的日志文件中,这里设置只打印error级别:
<!-- 错误日志 appender : 按照每天生成日志文件 -->
<appender name="SYT-ERROR-LOGGER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<!-- 过滤器,只记录 error 级别的日志 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<!-- 日志名称 -->
<file>${logging.position}${logging.path}/syt-error-log.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>${charset}</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天日志归档路径以及格式 -->
<fileNamePattern>${logging.position}${logging.path}/syt-error-log-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>1MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>30</maxHistory>
</rollingPolicy>
</appender>
可以从资料文件夹中复制完整的日志配置文件到service-util中
资料:资料>医院设置微服务>logback-spring.xml
文件中添加了节点,表示应用于特定的环境的日志配置,如下:
<!-- 开发环境 -->
<springProfile name="dev">
<!--
根logger,且只有一个level属性,定义日志输出的默认级别
如果微服务中配置了logging:level:xxx,那么微服务中设置的级别优先
如果微服务中没有配置日志级别,那么日志按照此处配置的级别输出
-->
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
</springProfile>
<!-- 生产环境 -->
<springProfile name="prod">
<!--
用来设置某一个包或者具体的某一个类的日志打印级别以及指定appender。
-->
<logger name="com.atguigu.syt" level="${logging.level}" additivity="false">
<appender-ref ref="SYT-LOGGER" />
<appender-ref ref="SYT-ERROR-LOGGER" />
</logger>
</springProfile>
修改service-hosp中的application.yml配置文件如下:则日志输出在控制台
spring:
application:
name: service-hosp
profiles:
active: dev,redis
修改service-hosp中的application.yml配置文件如下:则日志输出在文件中
spring:
application:
name: service-hosp
profiles:
active: prod,redis
在service-system中的application-dev中添加如下配置:
logging:
level:
root: info
file:
path: system
手机扫一扫
移动阅读更方便
你可能感兴趣的文章