尚医通-day06【医院模拟系统接口详细步骤】(内附源码)

发布时间 2023-06-12 19:23:55作者: 自律即自由-

第01章-医院系统

1、业务功能描述

资料:资料>医院模拟系统>尚医通API接口文档.docx

1.1、平台方

参考《尚医通API接口文档.docx》进行业务接口的开发,接收医院方的接口调用,将医院信息、科室信息、排班信息等数据存入MongoDB。

1.2、医院方

每个医院有自己的业务平台,需参考《尚医通API接口文档.docx》进行接口调用与数据上传,将本院的医院信息、科室信息、排班信息等数据上传到统一挂号平台。

2、部署医院端程序

2.1、创建yygh_manage数据库

资料:资料>医院模拟系统>yygh_manage.sql

2.2、运行医院程序

资料:资料>医院模拟系统>hospital-manage.zip

用idea打开医院端程序,修改数据库配置和maven配置 ,运行启动类

访问http://localhost:9998

2. 3、添加医院设置信息

平台端数据库guigu_syt_hosp中的hospital_set表中为每个需要接入平台的医院配置医院设置信息,例如“北京协和医院”,并生成医院编号和signKey

image-20230214130252914

医院端数据库guigu_yygh_manage中的hospital_set表配置医院设置信息,确保医院编号和signKey和平台端一致

image-20230214130217906

医院编号:是平台为医院颁发的,每个医院唯一。

signKey:是平台为医院颁发的,用于生成签名,确保数据传输过程的不可篡改。

第02章-接口安全

1、数字签名

1.1、对称加密

image-20230214233043970

1.2、非对称加密

image-20230214220611797

1.3、摘要算法

实现完整性的手段主要是摘要算法(Digest Algorithm),也就是我们常说的哈希函数。假设一个任意长的数据 z,经过哈希运算后会得到固定长度的数据 h,h 就是z的哈希结果又称作“数据指纹“ 或 “摘要”。MD5就是一个摘要算法摘要算法必须具有下面的 4 种特性:

不可逆:只有算法,没有秘钥,只能加密,不能解密
难题友好性:想要破解,只能暴力枚举
发散性:只要对原文进行一点点改动,摘要就会发生剧烈变化
抗碰撞性:原文不同,计算后的摘要也要不同

常见摘要算法:MD5、SHA1、SHA2(SHA224、SHA256、SHA384)

1.4、数字签名的原理

信息完整性

  • Bob有两把钥匙,一把公钥(public key),一把私钥(private key)
  • Bob把公钥送给他的朋友们,每人一把。Bob的私钥自己保留。

image-20230214220701239

Bob决定给Pat写一封信,要保证Pat收到信后,能够确认信的内容没有被篡改过,也就是需要保证信息的完整性。我们可以利用摘要运算,具体的流程是这样的:

  • 第一步:Bob写完信后,先用摘要算法(MD5、SHA),生成信件原文的摘要(Digest)
  • 第二步:Bob将摘要附在信件的原文下面,一起发给Pat

Pat收信后,也是两个步骤:

  • 第一步:Pat使用和Bob一样的摘要算法加密信件的原文得到信件的摘要。
  • 第二步:Pat将加密后的摘要,和Bob在原文中附加的摘要做对比,如果一致,说明信件没有篡改。

image-20230214215018998

问题:这种方式看似解决了数据完整性的问题,但是有一个致命的漏洞,就是,如果信件原文不加密,并且被黑客截获,黑客直接修改了原文,并且根据原文直接生成了新的摘要,然后附加在原文的下面,伪装成Bob将信件发给Pat,那么Pat接收后,是完全察觉不出来信件其实已经被篡改了。所以说摘要算法不具有机密性,如果明文传输,那么黑客可以修改消息后把摘要也一起改了,还是鉴别不出信息的完整性。

解决方案:我们可以使用非对称加密中的私钥,将摘要进行加密处理。

具体的流程是:

  • 第一步:Bob写完信后,先用摘要算法(MD5、SHA),生成信件的摘要(Digest)
  • 第二步:Bob使用自己的私钥,将摘要加密。加密后的结果,我们称之为“数字签名”Signature
  • 第三步:Bob将数字签名附在信的原文下面,一起发给Pat

Pat收信后,也是三个步骤:

第一步:Pat取下数字签名,用Bob的公钥解密,得到信件的摘要。

第二步:Pat使用和Bob一样的摘要算法加密信件的原文,得到信件的摘要。

第三步:将前面两部得到的摘要进行比对,如果一致,信件是就是Bob发的,并且没有经过篡改

这个过程叫做“验签”,也就是验证签名通过Bob的签名流程和Pat的验签流程,我们发现即使信件被黑客截获,即使黑客能够篡改信件原文,即使黑客能够通过摘要算法生成新的摘要,但是因为他没有Bob的私钥,因此无法对摘要进行加密,无法生成只有Bob才能生成的签名,所以信件也就无法被篡改了。微信支付中的签名和验签的过程就是这个原理。经过以上步骤,我们就可以取出信件原文了。

image-20230214215043784

  • 上面,我们讲解的数字签名的生成,这里使用了非对称加密+摘要算法的方式,在对安全性要求较高的系统中(如支付系统),会采用这种接口安全策略。
  • 尚医通项目中医院和平台间接口的调用对安全性的要求没有那么高,因此没有使用非对称加密的方式,使用了对称加密+摘要算法的方式。

2、获取签名密钥

2.1、引入依赖

service-util中引入依赖

<!--fastjson-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
</dependency>

2.2、工具类

将工具类复制到service-util的utils包中

资料:资料>医院模拟系统>工具类

2.3、签名的过程

尚医通的签名算法:参考《尚医通API接口文档.docx》 3.1传参说明

2.4、签名密钥的获取

接口:HospitalSetService

/**
     * 根据医院编码获取医院签名密钥
     * @param hoscode
     * @return
     */
String getSignKey(String hoscode);

/**
     * 根据hoscode获取医院设置
     * @param hoscode
     * @return
     */
HospitalSet getByHoscode(String hoscode);

实现:HospitalSetServiceImpl

@Override
public String getSignKey(String hoscode) {
    HospitalSet hospitalSet = this.getByHoscode(hoscode);
    if(hospitalSet == null) {
        throw new GuiguException(ResultCodeEnum.HOSPITAL_OPEN);
    }
    if(hospitalSet.getStatus().intValue() == 0) {
        throw new GuiguException(ResultCodeEnum.HOSPITAL_LOCK);
    }
    return hospitalSet.getSignKey();
}

@Override
public HospitalSet getByHoscode(String hoscode) {
    return baseMapper.selectOne(
        new LambdaQueryWrapper<HospitalSet>().eq(HospitalSet::getHoscode, hoscode)
    );
}

第03章-接口开发

1、上传医院信息接口

1.1、创建Repository

service-hosp中添加接口HospitalRepository

package com.atguigu.syt.hosp.repository;
public interface HospitalRepository extends MongoRepository<Hospital,ObjectId> {
    /**
     * 根据医院编号查询医院
     * @param hoscode
     * @return
     */
	Hospital findByHoscode(String hoscode);
}

1.2、创建Service

接口:HospitalService

package com.atguigu.syt.hosp.service;
public interface HospitalService {
     /**
     * 保存医院信息
     * @param paramMap
     */ 
    void save(Map<String, Object> paramMap);
}

实现:HospitalServiceImpl

package com.atguigu.syt.hosp.service.impl;

@Service
public class HospitalServiceImpl implements HospitalService {

    @Resource
    private HospitalRepository hospitalRepository;

    /**
     * 添加医院信息
     * @param paramMap
     */
    @Override
    public void save(Map<String, Object> paramMap) {
        //把paramMap变成对象
        Hospital hospital = JSONObject.parseObject(JSONObject.toJSONString(paramMap), Hospital.class);
        //根据医院编号查询医院信息是否已经添加
        Hospital existHospital = hospitalRepository.findByHoscode(hospital.getHoscode());
        //如果已经添加过,则修改
        if(existHospital != null) {
            //设置id
            hospital.setId(existHospital.getId());
            hospital.setStatus(existHospital.getStatus());
            hospitalRepository.save(hospital);
        } else {
            //如果没有添加,则添加
            //0:未上线 1:已上线
            hospital.setStatus(1);
            hospitalRepository.save(hospital);
        }
    }
}

1.3、创建Controller

在controller包中创建api包,创建ApiController类

package com.atguigu.syt.hosp.controller.api;

@Api(tags = "医院管理API接口")
@RestController
@RequestMapping("/api/hosp")
public class ApiController {

    @Resource
    private HospitalService hospitalService;

    @Resource
    private HospitalSetService hospitalSetService;

    @ApiOperation(value = "上传医院基本信息")
    @PostMapping("/saveHospital")
    public Result saveHospital(HttpServletRequest request) {
        Map<String, Object> paramMap = HttpRequestHelper.switchMap(request.getParameterMap());

        //签名验证
        String hoscode = (String)paramMap.get("hoscode");
        String signKey = hospitalSetService.getSignKey(hoscode);
        HttpRequestHelper.checkSign(paramMap, signKey);

        hospitalService.save(paramMap);
        return Result.ok();
    }
}

1.4、测试

运行医院端程序,测试医院信息的添加
img

医院端相关代码:hospital-manage:controller.ApiCOntroller.saveHospital

注意:在spring-security的AuthorizationWebSecurityConfig类中,我们配置了授权校验排除了api路径

/**
     * 配置哪些请求不拦截
     * 排除swagger相关请求
     * @param web
     * @throws Exception
     */
@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/api/**", "/favicon.ico","/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**", "/doc.html");
}

2、查询医院信息接口

2.1、ApiController

@ApiOperation(value = "获取医院信息")
@PostMapping("/hospital/show")
public Result hospital(HttpServletRequest request) {
    Map<String, Object> paramMap = HttpRequestHelper.switchMap(request.getParameterMap());

    //签名验证
    String hoscode = (String)paramMap.get("hoscode");
    String signKey = hospitalSetService.getSignKey(hoscode);
    HttpRequestHelper.checkSign(paramMap, signKey);

    Hospital hospital = hospitalService.getByHoscode(hoscode);
    return Result.ok(hospital);
}

2.2、Service

接口:HospitalService

/**
     * 根据医院编码获取医院信息
     * @param hoscode
     * @return
     */
Hospital getByHoscode(String hoscode);

实现:HospitalServiceImpl

@Override
public Hospital getByHoscode(String hoscode) {
    return hospitalRepository.findByHoscode(hoscode);
}

2.3、测试

测试查看医院信息:

image-20230215072650557

医院端相关代码:hospital-manage:controller.ApiCOntroller.getHospital

3、上传科室接口

3.1、创建Repository

service-hosp中添加接口DepartmentRepository

package com.atguigu.syt.hosp.repository;

public interface DepartmentRepository extends MongoRepository<Department,ObjectId> {
    //根据医院编号和科室编号查询科室
	Department findByHoscodeAndDepcode(String hoscode, String depcode);
}

3.2、创建Service

接口:DepartmentService

package com.atguigu.syt.hosp.service;

public interface DepartmentService {
     /**
     * 保存科室信息
     * @param paramMap
     */ 
    void save(Map<String, Object> paramMap);
}

实现:DepartmentServiceImpl

package com.atguigu.syt.hosp.service.impl;

@Service
public class DepartmentServiceImpl implements DepartmentService {

    @Resource
    private DepartmentRepository departmentRepository;

    @Override
    public void save(Map<String, Object> paramMap) {

        // 把paramMap变成对象
        Department department = JSONObject.parseObject(JSONObject.toJSONString(paramMap), Department.class);

        //查询科室是否存在  医院编号 + 科室编号
        Department existsDepartment = departmentRepository.findByHoscodeAndDepcode(
                department.getHoscode(),
                department.getDepcode());

        //判断
        if(existsDepartment != null) { //修改
            department.setId(existsDepartment.getId());
            departmentRepository.save(department);
        } else {
            departmentRepository.save(department);
        }
    }
}

3.3、添加Controller方法

在ApiController中添加方法

@Resource
private DepartmentService departmentService;

@ApiOperation(value = "上传科室")
@PostMapping("/saveDepartment")
public Result saveDepartment(HttpServletRequest request) {
    Map<String, Object> paramMap = HttpRequestHelper.switchMap(request.getParameterMap());

    //签名验证
    String hoscode = (String)paramMap.get("hoscode");
    String signKey = hospitalSetService.getSignKey(hoscode);
    HttpRequestHelper.checkSign(paramMap, signKey);

    departmentService.save(paramMap);
    return Result.ok();
}

3.4、测试

运行医院端程序,测试部门信息的添加

image-20230215124253926

4、查询科室接口

4.1、添加Service方法

接口:DepartmentService

/**
     * 分页查询
     * @param page 当前页码
     * @param limit 每页记录数
     * @param hoscode 查询条件
     * @return
     */
Page<Department> selectPage(int page, int limit, String hoscode);

实现:DepartmentServiceImpl

@Override
public Page<Department> selectPage(int page, int limit, String hoscode) {
    //设置排序规则
    Sort sort = Sort.by(Sort.Direction.ASC, "depcode");
    //设置分页参数
    Pageable pageable = PageRequest.of(page-1, limit, sort);
    //创建查询对象
    Department department = new Department();
    department.setHoscode(hoscode);
    Example<Department> example = Example.of(department);
    //执行查询
    Page<Department> pages = departmentRepository.findAll(example, pageable);
    return pages;
}

4.2、添加Controller方法

在ApiController中添加方法

@ApiOperation(value = "获取分页列表")
@PostMapping("/department/list")
public Result department(HttpServletRequest request) {
    Map<String, Object> paramMap = HttpRequestHelper.switchMap(request.getParameterMap());
    
    //签名验证
    String hoscode = (String)paramMap.get("hoscode");
    String signKey = hospitalSetService.getSignKey(hoscode);
    HttpRequestHelper.checkSign(paramMap, signKey);
    
    int page = Integer.parseInt((String)paramMap.get("page"));
    int limit = Integer.parseInt((String)paramMap.get("limit"));
    Page<Department> pageModel = departmentService.selectPage(page, limit, hoscode);
    return Result.ok(pageModel);
}

5、删除科室接口

5.1、添加Service方法

接口:DepartmentService

/**
     * 根据医院编码和科室编码删除科室
     * @param hoscode
     * @param depcode
     */
void remove(String hoscode, String depcode);

实现:DepartmentServiceImpl

@Override
public void remove(String hoscode, String depcode) {
    //根据医院编号、科室编号查询
    Department department = departmentRepository.findByHoscodeAndDepcode(hoscode, depcode);
    //判断查询出来数据,根据科室id删除
    if(department != null) {
        departmentRepository.deleteById(department.getId());
    }
}

5.2、添加Controller方法

@ApiOperation(value = "删除科室")
@PostMapping("/department/remove")
public Result removeDepartment(HttpServletRequest request) {
    Map<String, Object> paramMap = HttpRequestHelper.switchMap(request.getParameterMap());

    //签名验证
    String hoscode = (String)paramMap.get("hoscode");
    String signKey = hospitalSetService.getSignKey(hoscode);
    HttpRequestHelper.checkSign(paramMap, signKey);

    String depcode = (String)paramMap.get("depcode");
    departmentService.remove(hoscode, depcode);
    return Result.ok();
}

6、上传排班接口

6.1、创建Repository

package com.atguigu.syt.hosp.repository;

public interface ScheduleRepository extends MongoRepository<Schedule,ObjectId> {
    /**
     * 根据医院编码和医院端排版id查询排班记录
     * @param hoscode
     * @param hosScheduleId
     * @return
     */
    Schedule findByHoscodeAndHosScheduleId(String hoscode, String hosScheduleId);
}

6.2、创建Service

接口:ScheduleService

package com.atguigu.syt.hosp.service;
public interface ScheduleService {

    /**
     * 保存排班信息
     * @param paramMap
     */
    void save(Map<String, Object> paramMap);
}

实现:ScheduleServiceImpl

package com.atguigu.syt.hosp.service.impl;

@Service
public class ScheduleServiceImpl implements ScheduleService {

    @Resource
    private ScheduleRepository scheduleRepository;

    @Override
    public void save(Map<String, Object> paramMap) {

        //数据转换
        Schedule schedule = JSONObject.parseObject(JSONObject.toJSONString(paramMap), Schedule.class);
        //查询mongodb中数据是否存在
        Schedule existSchedule = scheduleRepository.findByHoscodeAndHosScheduleId(
                schedule.getHoscode(),
                schedule.getHosScheduleId()
        );

        //如果不存在
        if(existSchedule != null){//更新
            schedule.setId(existSchedule.getId());
            scheduleRepository.save(schedule);
        }else{//新增
            scheduleRepository.save(schedule);
        }
    }
}

6.3、添加Controller

ApiController

@Resource
private ScheduleService scheduleService;

@ApiOperation(value = "上传排班")
@PostMapping("/saveSchedule")
public Result saveSchedule(HttpServletRequest request) {
    Map<String, Object> paramMap = HttpRequestHelper.switchMap(request.getParameterMap());

    //签名验证
    String hoscode = (String)paramMap.get("hoscode");
    String signKey = hospitalSetService.getSignKey(hoscode);
    HttpRequestHelper.checkSign(paramMap, signKey);

    scheduleService.save(paramMap);
    return Result.ok();
}

7、查询排班接口

7.1、添加Service方法

接口:ScheduleService

/**
     * 分页查询
     * @param page 当前页码
     * @param limit 每页记录数
     * @param hoscode 查询条件
     * @return
     */
Page<Schedule> selectPage(int page, int limit, String hoscode);

实现:ScheduleServiceImpl

@Override
public Page<Schedule> selectPage(int page, int limit, String hoscode) {
    //设置排序规则
    Sort sort = Sort.by(Sort.Direction.ASC, "workDate");
    //设置分页参数
    Pageable pageable = PageRequest.of(page-1, limit, sort);
    //创建查询对象
    Schedule schedule = new Schedule();
    schedule.setHoscode(hoscode);
    Example<Schedule> example = Example.of(schedule);
    Page<Schedule> pages = scheduleRepository.findAll(example, pageable);
    return pages;
}

7.2、添加Controller方法

@ApiOperation(value = "获取排班分页列表")
@PostMapping("/schedule/list")
public Result schedule(HttpServletRequest request) {
    Map<String, Object> paramMap = HttpRequestHelper.switchMap(request.getParameterMap());

    //签名验证
    String hoscode = (String)paramMap.get("hoscode");
    String signKey = hospitalSetService.getSignKey(hoscode);
    HttpRequestHelper.checkSign(paramMap, signKey);

    int page = Integer.parseInt((String)paramMap.get("page"));
    int limit = Integer.parseInt((String)paramMap.get("limit"));
    Page<Schedule> pageModel = scheduleService.selectPage(page, limit, hoscode);
    return Result.ok(pageModel);
}

8、删除排班接口

8.1、添加Service

接口:ScheduleService

/**
     * 根据医院编码和排班id删除排班
     * @param hoscode
     * @param hosScheduleId
     */
void remove(String hoscode, String hosScheduleId);

实现:ScheduleServiceImpl

@Override
public void remove(String hoscode, String hosScheduleId) {
    Schedule schedule = scheduleRepository.findByHoscodeAndHosScheduleId(hoscode, hosScheduleId);
    if(null != schedule) {
        scheduleRepository.deleteById(schedule.getId());
    }
}

8.2、添加Controller接口

@ApiOperation(value = "删除排班")
@PostMapping("/schedule/remove")
public Result removeSchedule(HttpServletRequest request) {
    Map<String, Object> paramMap = HttpRequestHelper.switchMap(request.getParameterMap());

    //签名验证
    String hoscode = (String)paramMap.get("hoscode");
    String signKey = hospitalSetService.getSignKey(hoscode);
    HttpRequestHelper.checkSign(paramMap, signKey);

    String hosScheduleId = (String)paramMap.get("hosScheduleId");
    scheduleService.remove(hoscode, hosScheduleId);
    return Result.ok();
}

源码:https://gitee.com/dengyaojava/guigu-syt-parent