尚医通项目实现01

1. service-hosp 医院模块开发#

1.1 需求#

  • 医院设置主要是用来保存开通医院的一些基本信息,每个医院一条信息,保存了医院编号(平台分配,全局唯一)和接口调用相关的签名key等信息,是整个流程的第一步,只有开通了医院设置信息,才可以上传医院相关信息。我们所开发的功能就是基于单表的一个CRUD、锁定/解锁和发送签名信息这些基本功能。

  • 表结构

Copy Highlighter-hljs
CREATE TABLE `hospital_set` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号', `hosname` varchar(100) DEFAULT NULL COMMENT '医院名称', `hoscode` varchar(30) DEFAULT NULL COMMENT '医院编号', `api_url` varchar(100) DEFAULT NULL COMMENT 'api基础路径', `sign_key` varchar(50) DEFAULT NULL COMMENT '签名秘钥', `contacts_name` varchar(20) DEFAULT NULL COMMENT '联系人', `contacts_phone` varchar(11) DEFAULT NULL COMMENT '联系人手机', `status` tinyint(3) NOT NULL DEFAULT '0' COMMENT '状态', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `is_deleted` tinyint(3) NOT NULL DEFAULT '0' COMMENT '逻辑删除(1:已删除,0:未删除)', PRIMARY KEY (`id`), UNIQUE KEY `uk_hoscode` (`hoscode`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='医院设置表';
  • 后端实体类结构
Copy Highlighter-hljs
@Data public class BaseEntity implements Serializable { @ApiModelProperty(value = "id") @TableId(type = IdType.AUTO) // 该注解用于将某个成员变量指定为数据表主键 private Long id; @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 // @TableLogic 注解用于实现数据库数据逻辑删除 @TableField("is_deleted") private Integer isDeleted; @ApiModelProperty(value = "其他参数") @TableField(exist = false) private Map<String,Object> param = new HashMap<>(); }

1.2 代码实现#

1.2.1 使用mybatis plus 实现mapper接口#

Copy Highlighter-hljs
@Repository @Mapper public interface HospitalSetMapper extends BaseMapper<HospitalSet> { // 使用mybatis plus 需继承BaseMapper, }
  • 项目可能涉及到复杂的数据库操作,如联表查询等,需要用自定义SQL语句操作
    • 在 Java 包下创建 xxxMapper.java 接口类,然后再 resources 资源包下创建对应的 xxxMapper.xml 文件;
    • 创建好 .java 和 .xml 文件后,在 Java 文件编写接口方法,然后再 xml 文件中编写对应方法的 SQL 语句;
    • 当调用接口中方法后,Mybatis 就会去 xml 文件中找到对应的 SQL。

1.2.2 Service接口#

Copy Highlighter-hljs
public interface HospitalSetService extends IService<HospitalSet> { }

1.2.3 ServiceImpl#

Copy Highlighter-hljs
@Service public class HospitalSetServiceImpl extends ServiceImpl<HospitalSetMapper, HospitalSet> implements HospitalSetService { @Autowired private HospitalSetMapper hospitalSetMapper; }

1.2.4 Controller#

Copy Highlighter-hljs
@Api(tags = "医院设置管理") @RestController // 相当于@Controller 和 @ResponseBody两个注解合并, @RequestMapping("/admin/hosp/hospitalSet") public class HospitalSetController { // 注入service @Autowired private HospitalSetService hospitalSetService; // http://localhost:8201/admin/hosp/hospitalSet/ // 3. 条件查询带分页 @ApiOperation(value = "条件查询带分页") @PostMapping("findPageHospSet/{current}/{limit}") public Result findPageHospSet (@PathVariable long current, @PathVariable long limit, @RequestBody(required = false)HospitalSetQueryVo hospitalSetQueryVo){ // 此处使用VO 对象 代替 DTO 接收前端的查询值 // 创建page对象,传递当前页,每页记录数 Page<HospitalSet> page = new Page<>(current,limit); // 构建条件 // QueryWrapper 是mybatis plus 实现查询的对象封装操作类 QueryWrapper<HospitalSet> wrapper = new QueryWrapper<>(); String hosname = hospitalSetQueryVo.getHosname(); String hoscode = hospitalSetQueryVo.getHoscode(); if(!StringUtils.isEmpty(hosname)){ wrapper.like("hosname",hospitalSetQueryVo.getHosname()); // 此处第一个参数 column 需要为数据库中的真实字段 } if(!StringUtils.isEmpty(hoscode)){ wrapper.eq("hoscode",hospitalSetQueryVo.getHoscode()); } // 调用方法实现分页查询 IPage<HospitalSet> pageHospitalSet = hospitalSetService.page(page,wrapper); return Result.ok(pageHospitalSet); }

条件分页功能实现,需要在config中加载分页插件

Copy Highlighter-hljs
@Configuration @MapperScan("com.wxz.hospital.hosp.mapper") public class HospConfig { /** * 分页插件 */ @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } }

2. 管理平台前端搭建#

  • git 上下载 vue-admin-template-master,
Copy Highlighter-hljs
npm install --global windows-build-tools --save npm install node-sass@4.12.0 --save npm rebuild node-sass # 一般不需这一步 npm run dev
  • ./build/dev.env.js
Copy Highlighter-hljs
module.exports = merge(prodEnv, { NODE_ENV: '"development"', // BASE_API: '"https://easy-mock.com/mock/5950a2419adc231f356a6636/vue-admin"', BASE_API: '"http://localhost:8201"', // 此处设置连接的后端项目地址 })

image-20220814160010402

image-20220814160059985

2.1 医院管理功能#

2.1.1 添加路由#

  • src/router/index.js 添加路由
Copy Highlighter-hljs
export const constantRouterMap = [ { path: '/hospSet', component: Layout, redirect: '/hospSet/hospital/list', name: 'hospital', meta: { title: '医院管理', icon: 'example' }, children: [ { path: 'hospitalSet/list', name: '医院设置查看', component: () =>import('@/views/hosp/hospitalSet/list'), // 设置要跳转的路径 meta: { title: '查看',icon: 'table' } }, { path: 'hospitalSet/add', name: '医院设置添加', component: () =>import('@/views/hosp/hospitalSet/form'), meta: { title: '添加',icon: 'table' } }, { path: 'hospitalSet/edit/:id', name: '医院设置修改', component: () =>import('@/views/hosp/hospitalSet/form'), meta: { title: '编辑',noCache: true }, hidden: true }, { path: 'hosp/list', name: '医院列表', component: () =>import('@/views/hosp/hospital/list'), meta: { title: '医院列表', icon: 'table' } }, { path: 'hospital/show/:id', name: '查看', component: () => import('@/views/hosp/hospital/show'), meta: { title: '查看', noCache: true }, hidden: true }, { path: 'hospital/schedule/:hoscode', name: '排班', component: () => import('@/views/hosp/hospital/schedule'), meta: { title: '排班', noCache: true }, hidden: true } ] } ] export default new Router({ // mode: 'history', //后端支持可开 scrollBehavior: () => ({ y: 0 }), routes: constantRouterMap })

2.1.2 定义接口路径#

  • src/api/hosp/hospitalSet.js 中定义接口路径
Copy Highlighter-hljs
import request from '@/utils/request' //可以理解为导入的公共包 const api_name = '/admin/hosp/hospitalSet' //从java对应的controller复制过来! export default { getPageList(current, limit, searchObj) { return request({ url: `${api_name}/findPageHospSet/${current}/${limit}`,//这里前边还会拼上dev.env.js文件中的BASE_API method: 'post', data: searchObj //使用json传递参数 用data 其他用params }) }, deleteHospSet(id) { return request ({ url: `${api_name}/${id}`, method: 'delete' }) }, removeRows(idList) { return request({ url: `${api_name}/batchRemove`, method: 'delete', data: idList }) }, //锁定和取消锁定 lockHospSet(id,status) { return request ({ url: `${api_name}/lockHospitalSet/${id}/${status}`, method: 'put' }) }, //添加医院设置 saveHospSet(hospitalSet) { return request ({ url: `${api_name}/saveHospitalSet`, method: 'post', data: hospitalSet }) }, //医院院设置 根据id查询 getHospSet(id) { return request ({ url: `${api_name}/getHospSet/${id}`, method: 'get' }) }, //修改医院设置 updateHospSet(hospitalSet) { return request ({ url: `${api_name}/updateHospitalSet`, method: 'post', data: hospitalSet }) } }

2.1.3 定义页面组件脚本#

  • src/views/hosp/hospitalSet/list.vue
Copy Highlighter-hljs
<el-button type="primary" icon="el-icon-search" @click="getList()">查询</el-button> <el-button type="danger" size="mini" @click="removeRows()">批量删除</el-button> <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="primary" size="mini" icon="el-icon-delete" @click="lockHostSet(scope.row.id,0)">锁定</el-button> <el-button v-if="scope.row.status==0" type="danger" size="mini" icon="el-icon-delete" @click="lockHostSet(scope.row.id,1)">取消锁定</el-button> <router-link :to="'/hospSet/hospitalSet/edit/'+scope.row.id"> <el-button type="primary" size="mini" icon="el-icon-edit">编辑</el-button> </router-link> <script> import hospitalSetApi from '@/api/hosp/hospitalSet' //即前面定义的那个api下的js export default { /* 下面的代码是有结构的 data(){ return{};}, created(){}, methods:{} */ // 定义变量和初始值 data() { return { //(page, limit, searchObj)参考这里传递的参数进行定义变量 current:1, //当前页 limit:3, //每页显示记录数 searchObj:{ hosname:'',hoscode:''}, //条件封装对象 list:[], //这个是用来接受返回的列表的 total:0, multipleSelection:[] } }, // 页面渲染之前执行 : 一般用来调用methods定义的方法,得到数据 created() { //只能调用当前vue的方法,不能直接调用到 hospitalSetApi.getPageList的 this.getList() }, methods: { //定义方法,进行请求接口调用 getList(page=1){ //不传默认第1页 //前面已经 import hospitalSetApi from '@/api/hosp/hospitalSet' this.current = page //使用hospitalSetApi调用方法即可 , 这个方法是暴露出来的 hospitalSetApi.getPageList(this.current,this.limit,this.searchObj) .then(response=>{ this.list = response.data.records this.total = response.data.total }) .catch(error=>{ console.log(error) }) }, //删除医院设置的方法 removeDataById(id){ this.$confirm('此操作将永久删除医院是设置信息,是否继续?','提示',{ confirmButtonText:'确实', cancelButtonText:'取消', type:'warning' }).then(()=>{ //确定执行then方法 //调用删除接口 hospitalSetApi.deleteHospSet(id) .then(response=>{ this.$message({ type:'success', message:'删除成功!' }) //刷新页面 this.getList(1) }) }) }, handleSelectionChange(selection) { this.multipleSelection = selection }, //批量删除 removeRows(){ this.$confirm('此操作将永久删除医院是设置信息, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { //确定执行then方法 var idList = [] //遍历数组得到每个id值,设置到idList里面 for(var i=0;i<this.multipleSelection.length;i++) { var obj = this.multipleSelection[i] var id = obj.id idList.push(id) } //调用接口 hospitalSetApi.removeRows(idList) .then(response => { //提示 this.$message({ type: 'success', message: '删除成功!' }) //刷新页面 this.getList(1) }) }) }, lockHostSet(id,status) { hospitalSetApi.lockHospSet(id,status) .then(response => { //刷新 this.getList(this.current) }) } } } </script>
  • 此时点击医院列表http://localhost:9528/#/hospSet/hosp/list, 请求地址为http://localhost:8201/admin/hosp/hospital/list/1/10 , 地址正确,但无法得到正确值, 此时就遇到了跨域的问题

2.1.4 跨域处理#

跨域:浏览器对于javascript的同源策略的限制 。

以下情况都属于跨域:

跨域原因说明 实例
域名不同 www.jd.com 与 www.taobao.com
域名相同,端口不同 www.jd.com:8080 与 www.jd.com:8081
二级域名不同 item.jd.com 与 miaosha.jd.com

如果域名和端口都相同,但是请求路径不同,不属于跨域,而我们刚才是从localhost:9528去访问localhost:8201,这属于端口不同,跨域了。

解决跨域: 在controller 上添加注解 @CrossOrigin

2.2 数据管理功能#

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

2.2.1 数据字典表结构#

Copy Highlighter-hljs
CREATE TABLE `dict` ( `id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'id', `parent_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '上级id', `name` varchar(100) NOT NULL DEFAULT '' COMMENT '名称', `value` bigint(20) DEFAULT NULL COMMENT '值', `dict_code` varchar(20) DEFAULT NULL COMMENT '编码', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `is_deleted` tinyint(3) NOT NULL DEFAULT '1' COMMENT '删除标记(0:不可用 1:可用)', PRIMARY KEY (`id`), KEY `idx_dict_code` (`dict_code`), KEY `idx_parent_id` (`parent_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='组织架构表';

parent_id:上级id,通过id与parent_id构建上下级关系,例如:我们要获取所有行业数据,那么只需要查询parent_id=20000的数据

name:名称,例如:填写用户信息,我们要select标签选择民族,“汉族”就是数据字典的名称

value:值,例如:填写用户信息,我们要select标签选择民族,“1”(汉族的标识)就是数据字典的值

dict_code:编码,编码是我们自定义的,全局唯一,例如:我们要获取行业数据,我们可以通过parent_id获取,但是parent_id是不确定的,所以我们可以根据编码来获取行业数据

2.2.2 实体类Dict结构#

Copy Highlighter-hljs
@Data @ApiModel(description = "数据字典") @TableName("dict") public class Dict { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "id") private Long id; @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 Map<String,Object> param = new HashMap<>(); @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 = "是否包含子节点") @TableField(exist = false) // mysql 表中没有此字段 为了树形目录显示创建 private boolean hasChildren; }
  • Spring Cache 缓存注解

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

@CacheEvict

使用该注解标志的方法,会清空指定( value 缓存名) 的缓存。一般用在更新或者删除方法上

2.2.2 service-cmn 模块#

后端搭建service-cmn模块,用于实现数据字典功能.

  1. 结合mybatis plus 添加service-cmn模块的mapper, service, serviceImpl, controller
  • 实现数据字典列表功能

  • 实现数据字典导入导出功能

  • 数据字典不需要什么改动, 将其添加入缓存中, 可大幅提升访问速度

数据字典中的数据一般不需要进行改动, 选择从本地的excel 表格中导入导出, 作为保存方法. 操作excel表格选择EasyExcel插件

Copy Highlighter-hljs
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> </dependency>
  • controller
Copy Highlighter-hljs
@Api(tags = "数据字典接口") @RestController @RequestMapping("/admin/cmn/dict") @CrossOrigin public class DictController { @Autowired private DictService dictService; @ApiOperation(value = "根据id获取查询子数据列表") @GetMapping("findChildData/{id}") public Result findChildData(@PathVariable Long id){ List<Dict> childData = dictService.findChildData(id); return Result.ok(childData); } @ApiOperation(value = "导入数据字典") @PostMapping("importData") public Result importData(MultipartFile file){ dictService.importDictData(file); return Result.ok(); } @ApiOperation(value = "导出数据字典") @GetMapping("exportData") public void exportData(HttpServletResponse response){ dictService.exportDictData(response); } }
  • serviceImpl
Copy Highlighter-hljs
// 2. 导出数据字典接口 @Override public void exportDictData(HttpServletResponse response) { // 设置下载信息 response.setContentType("application/vnd.ms-excel"); response.setCharacterEncoding("utf-8"); String fileName = "dict"; 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("dict").doWrite(dictEeVoList); } catch (IOException e) { e.printStackTrace(); } } // 3. 导入数据字典 @Override public void importDictData(MultipartFile file) { try { EasyExcel.read(file.getInputStream(),DictEeVo.class, new DictListener(baseMapper)).sheet().doRead(); } catch (IOException e) { e.printStackTrace(); } }

2.2.3 前端模块#

  • 数据字典列表 (树形显示层级关系)

load绑定点击箭头后的函数 tree-pros 是否显示箭头 在目录树中点击当前节点, 调用 getChildren函数, 获取其子节点并显示

Copy Highlighter-hljs
<el-table :data="list" style="width: 100%" row-key="id" border lazy :load="getChildren" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }">
  • 数据导入导出

点击导出, 浏览器会在一个新打开未命名的窗口载入目标文档 target="_blank"

Copy Highlighter-hljs
<a href="http://localhost:8202/admin/cmn/dict/exportData" target="_blank"> <el-button type="text"><i class="fa fa-plus" /> 导出</el-button> </a>

点击导入, 执行点击事件importData, 该事件函数将dialogImportVisible 置为true, 出现文件选择对话框, 点击确定后, 发送给 http://localhost:8202/admin/cmn/dict/importData

Copy Highlighter-hljs
<el-button type="text" @click="importData"><i class="fa fa-plus" /> 导入</el-button> <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="'http://localhost:8202/admin/cmn/dict/importData'" 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> <script> export default { data() { return { list: [], //数据字典列表数组 dialogImportVisible: false //设置弹框是否弹出 } }, created() { this.getDictList(1) // 1 是sql中的全部分裂id }, methods: { importData() { this.dialogImportVisible = true }, //上传成功调用方法, onUploadSuccess(response, file) { this.$message.info('上传成功') //关闭弹框 this.dialogImportVisible = false //刷新页面 this.getDictList(1) } } </script>

2.2.4 引入nginx#

目前我们共有两个项目, 地址分别为 http://localhost:8201http://localhost:8202, 在前端难以实现通过同一个BASE_API 来访问两个域, 此时通过nginx 作为 反向代理服务器来实现统一api接口

反向代理,其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问,我们只需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器IP地址

  • nginx.conf 中添加以下配置
Copy Highlighter-hljs
server { listen 9001; server_name localhost; location ~ /hosp/ { proxy_pass http://localhost:8201; } location ~ /cmn/ { proxy_pass http://localhost:8202; } }
posted @   Firewooood  阅读(517)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示
CONTENTS