移动护理总结
移动护理总结:
项目架构:
使用JFinal 为基础 JBolt为框架进行开发,前后端分类
官网:
数据库:
- mysql 8.0
- SQL Server 2008
构建工具:
maven:
版本控制:
git+腾讯工蜂
服务器部署
ubuntu
接口管理
Apifox
内网穿透
花生壳构建测试环境
APIFOX配置测试环境:
项目结构:
接口层级目录是以Controller层的包层级进行管理的
数据库文档设计
操作手册:
飞书链接: https://yzqhs04oeu.feishu.cn/docs/doccnmxOlulCDYM7icNY4MdTZhg 密码: wnJW
数据库接口文档:
飞书链接: https://yzqhs04oeu.feishu.cn/docs/doccnpYLJh255P67hsnWCzsnm0f 密码: jwQU
开发环境及流程配置:
项目需要映射SQLServer 与 项目端口地址:
配置测试环境:
开发流程:
根据业务需求,商量初始界面之后,进行mock数据,就是先将所需要的json数据模拟出来,这样前后端都可以快速进行开发。通过APIFox工具可以构建
前端将请求切换至mock 域名,就可以拿到数据了 mock.js语法
Mock数据的使用:
https://www.apifox.cn/help/app/mock/mock-custom-scripts/#
搭建测试环境:
1 | package Config; |
写完接口之后能够进行启动测试,使用JunitFinal进行测试。
用户登录设计:
用户登录基本功能:
需求
用户需要token验证,存在时效性
需要不能重复登录
需要记录用户设置的手机,所有推送只会在用户最后登录的手机上显示
登录设计:
1. 普通令牌
普通令牌是SpringSecurityOauth2给客户端颁发的一个无含义的令牌,在令牌发布时,SpringSecurityOauth2将用户信息存储到程序指定的存储位置,并用普通令牌唯一标识这个存储信息,当用户再次携带令牌访问时,SpringSecurityOauth2会根据令牌查询用户信息,进而实现权限角色的限制。
普通令牌需要一个存储用户信息的地方,这个地方可以内存,也可以是数据库(Redis、Mysql)。
基于数据库存储(以Redis为例)
①基于Redis存储用户信息的方式,认证服务器将用户信息存储到指定的Redis数据库中
②当资源服务获取到access_token时,会到Redis中获取用户信息
③在微服务场景下适用
2. JWT令牌
jwt令牌的方式就无需数据库的介入,jwt令牌中就包含着用户的信息,SpringSecurityOauth在发布令牌时,会将用户信息放入JWT令牌中,用户拿着JWT令牌时,SpringSecurityOauth从中获取到用户信息,实现用户权限的控制。
jwt不需要后端进行存储。
①基于JWT令牌的认证服务器,用户信息存储到令牌中
②当资源服务获取到access_token后,会解析这个jwt类型的access_token,从中会获取到用户信息③微服务场景下也不适用
对比相应的业务,护理app有较高的安全认证需求,因为之前使用jwt+redis进行登录,因要求不需要redis ,因此采用jwt +用户在线表的方式进行登录,jwt的负载中记录用户id 和cid信息 ,再拦截其中进行解析用户信息,判断是否有效,数据库中记录过期时间,定时任务每个一分钟清理过期用户。
用户登录分为两个部分,token签发以及认证
数据库在线用户表:
用户登录模块
- 进行密码校验
- 判断用户是否在线,如果在线,判断设备号是否是相同的。如果不同,则是异端登录,重新生成token,并将状态设置为异端登录
- 如果没有在线,生成token ,并将用户信息返回给前端。
1 | /** |
JWT工具类:
1 | package medical.appsupport.util; |
拦截器进行拦截流程
判断用户是否有效
获取用户信息
查询用户是否处在登录中
查询数据库判断是否是有效状态(可以管理端强制退出)
判断是否是同一设备名,如不是账户已在其他登录,是否重新登录
如果在活跃时间类,重新生成token,返回前端,自动延长时间
1 | package medical.appsupport.Interceptor; |
开启拦截器:
1 | /** |
定时任务扫描,清理离线用户:
1 | /** |
定时扫描离线用户:
1 | public class JBoltOnlineUserClearTask implements ITask { |
利用Ehcache缓存各科室数据
Ehcache
Ehcache 简介:
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点。是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。另外Spring 提供了对缓存功能的抽象:即允许绑定不同的缓存解决方案(如Ehcache),但本身不直接提供缓存功能的实现。它支持注解方式使用缓存,非常方便。
通过在内存里维护一个统一的巨大的hash表,它能够用来存储各种格式的数据,包括图像、视频、文件以及数据库检索的结果等。简单的说就是将数据调用到内存中,然后从内存中读取,从而大大提高读取速度。
Ehcache的特点:
(1)快速简单,具有多种缓存策略
(2)缓存数据有两级为内存和磁盘,缓存数据会在虚拟机重启的过程中写入磁盘
(3)可以通过RMI、可插入API等方式进行分布式缓存
(4)具有缓存和缓存管理器的侦听接口
(5)支持多缓存管理器实例,以及一个实例的多个缓存区域。并提供Hibernate的缓存实现
Ehcache配置:
Ehcache支持通过xml文件和API两种方式进行配置。
xml方式
Ehcache的CacheManager构造函数或工厂方法被调用时,会默认加载classpath下名为ehcache.xml的配置文件。如果加载失败,会加载Ehcache jar包中的ehcache-failsafe.xml文件,这个文件中含有简单的默认配置。
ehcache.xml配置参数说明
name:缓存名称。
maxElementsInMemory:缓存最大个数。
eternal:缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期。
timeToIdleSeconds:置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:缓存数据的生存时间(TTL),也就是一个元素从构建到消亡的最大时间间隔值,这只能在元素不是永久驻留时有效,如果该值是0就意味着元素可以停顿无穷长的时间。
maxEntriesLocalDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
overflowToDisk:内存不足时,是否启用磁盘缓存。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
maxElementsOnDisk:硬盘最大缓存个数。
diskPersistent:是否在VM重启时存储硬盘的缓存数据。默认值是false。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
timeToLiveSeconds –>当对象自从被存放到缓存中后,如果处于缓存中的时间超过了 timeToLiveSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清除;即缓存自创建日期起能够存活的最长时间,单位为秒(s)
timeToIdleSeconds –> 当对象自从最近一次被访问后,如果处于空闲状态的时间超过了timeToIdleSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清空;即缓存被创建后,最后一次访问时间到缓存失效之时,两者之间的间隔,单位为秒(s)
timeToLiveSeconds必须大于timeToIdleSeconds才有意义。
1 | ehcache.xml的一个范例 |
API方式
xml配置的参数也可以直接通过编程方式来动态的进行配置(dynamicConfig没有设为false)。
- jfianl
1 | Cache cache = manager.getCache("sampleCache"); |
- 其他
1 | CacheManager manager = CacheManager.newInstance("src/config/ehcache.xml"); |
测试用例:
(37条消息) ehcache缓存过期时间和注解的使用。shuixiou1的博客-CSDN博客ehcache过期时间
需求:
在app中,需要查询在院的科室患者,因此,可以在用户第一次查询时,将用户信息保存在缓存中 。
为方便管理建立一个工具类,将需要缓存的数据放在该类中(使用了缓存一定要考虑清除缓存):
MyCacheKit
1 | package medical.appsupport.util; |
枚举类:保存键值
1 | public enum CacheKeyConstants { |
工具类设计:
单例设计模式
单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)。就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
就是类在内存中只能存在一个实例对象
单例模式的结构
单例模式的主要有以下角色:
- 单例类。只能创建一个实例的类
- 访问类。使用单例类
单例模式的实现
单例设计模式分类两种:
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
饿汉式
所谓饿汉式,就是直接创建出类的实例化,然后用private私有化,对外只用静态方法暴露。
静态变量
步骤
- 构造器私有化
- 类的内部创建对象
- 向外暴露一个静态的公共方法
优点 | 缺点 |
---|---|
写法简单,在类装载的时完成实例化,避免了线程同步问题 | 类装载时完成实例化,没有达到LazyLoading的效果,若该实例从未使用,则会造成内存浪费 |
写单例模式的步骤: 以下是饿汉式的写法:
(1)必须在该类中,自己先创建出一个对象,私有化;
(2)私有化自身的构造器,防止外界通过构造器创建新的对象。
(3)向外暴露一个公共静态方法用于获取自身的对象;
1 | public class MyUtil{ |
该方式在成员变量MyUtil类型的静态变量,并创建MyUtil类的对象instance。
MyUtil对象是随着类的加载而创建的。如果类对象比较大的话会造成内存的浪费
静态代码块
Singleton.java
1 | public class Singleton { |
1 | public class Client { |
该方式在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。存在内存浪费的问题
懒汉式:
线程不安全
1 | public class StringUtil { |
成员变量位置声明 StringUtil 类型的成员变量,并没有进行对象的赋值操作
单调用getInstance()方法时候获取StringUtil 创建 StringUtil 类的对象,这样就实现了懒加载的效果
但是,如果是多线程环境,就会出现线程安全的问题
方式二:线程安全
1 |
|
实现了懒加载效果,但是在synchronized关键字,导致方法的执行效果特别低
可以看出,只有在初始化instance的时候才会出现线程安全的问题,一旦初始化之后就不存在了
方式三: (双重锁检验)
从上个样例中可以看出,绝大部分操作都是读操作,读操作的线程是安全的,所以没有必要让每个线程都必须持有锁才能调用该方法,所以需要调整加锁的时机
双重检查锁机制
1 | public class StringUtil { |
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,
在多线程的情况下,可能会出现空指针问题: JVM在实例化对象的时候会进行优化和指令重排序操作,
只需要使用
volatile
关键字
volatile
关键字可以保证可见性和有序性
静态内部类
静态内部类实现单例模式,是借助了类加载器加载类的特性实现的,类加载器在加载类时使用了synchronized关键字.
1 | public class SingletonDemo { |
输出为:
1 | true |
关于sqlserver二开数据源设置:
填写二开数据源
切换数据源:
1 |
|
数据库分页操作:
1 | /** |
分页数据类:
1 | /** |
自定义注解实现参数校验,并处理异常
注解的生命周期有三个阶段:1、Java源文件阶段;2、编译到class文件阶段;3、运行期阶段。
导入hiberbate框架:
1 | <!-- validator --> |
自定义注解
ParamException
1 | public class ParamException extends RuntimeException{ |
ExceptionConfig
1 | package medical.appsupport.Interceptor; |
定义拦截器,拦截指定注解:
ParaExceptionInterceptor
1 | package medical.appsupport.Interceptor; |
获取拦截的错误:
1 | package medical.appsupport.util; |
在需要的类上添加vo上添加注解,在需要校验的方法类进行
1 |
|
Java 反射详解 - YSOcean - 博客园 (cnblogs.com)
前后端跨域问题解决
后端如何解决跨域问题_CrazySnail_x的博客-CSDN博客_后端跨域
1 | package medical.appsupport.Interceptor; |
利用定时任务拆解医嘱:
定时任务扫描类:
1 | public class AdviceSplitTask implements ITask { |
配置自动调度
1 | /** |
编写查询sql
1 | #sql("selectFluidAdvice") |
每隔5分钟进行一次调度
1 | /** |
1 | /** |
定时任务处理病区统计:
每隔二十分钟进行一次病区统计,将统计的数据存放至缓存中,提高并发量
1 | import com.jfinal.aop.Aop; |
1 | /** |
1 | package cn.jbolt.common.util; |
备忘录:
使用unipush进行个推服务
官网
- 通知类型表
- 通知日志表
每隔一分钟进行扫描数据库,并进行通知,
1 |
|
1 | public static void PushMemon(MobMemo mobMemo, MobMsgLog memoMsgLog) { |
获取备忘录的历史消息
1 | /** |
已读未读设计
1 | /** |
生命体征配置:
配置表中写入用户需要查询的数据,记录接口数据等
1 | public Page<LoginLog> paginateAdminList(Integer pageNumber, Integer pageSize, String keywords, Date startTime, |
医嘱筛选
1 | /** |
对医嘱数据进行替换
1 | /** |
获取元数据:
1 | /** |
1 | // 通过日期获取指定的医嘱信息 |
按需筛选医嘱
1 | /** |
医嘱执行
医嘱执行的目的是,通过医嘱医嘱执行接口,将dbo.JD_DCMX 中患者的医嘱进行修改
如果是 长期医嘱 修改字段
D_KDCZYBH(关联用户id的编号)
D_KDCZYXM(姓名)
D_ZXSJ(时间)
如果是短期医嘱:
修改
D_ZXQM(进行签名操作)
D_ZXSJ(时间)
短期医嘱进行双签名操作
双签名操作更新时间以及 对D_ZXQM 字段进行拼接操作 。
问题一:
1、医嘱执行与签名是不需要修改的医嘱的状态嘛,只需要填上执行人/签名即可,那我怎么进行筛选呢?
通过判断写入字段是否为空嘛
2、医嘱执行业务只有以下操作吗
长期医嘱进行的是执行操作,只有一次,短期医嘱进行执行与签名操作,存在两次操作(一次进行执行操作,第二次进行签名操作)
3、通过什么判断医嘱执行与未执行
医嘱未执行
未执行:ORDER_STATUS = G
长期已执行: D_KDCZYBH(关联用户id的编号)、 D_KDCZYXM(姓名) 不为空值
短期已执行: D_ZXQM(进行签名操作)、D_ZXSJ(时间) 不为空值
4、签名与未签名医嘱筛选
V:未签名
未签名: (ORDER_STATUS= V )&& (D_ZXQM ==null)
双签名:(ORDER_STATUS= V )&& (D_ZXQM !=null)
病人主页
1 | SELECT |
1 | public List<JSONObject> findAllPatByAssociateId(List<String> patIds, String deptCode) { |
1 |
|
要求业务一:
我们现在进行医嘱执行、签名、停止三个功能
长期、短期医嘱
不清楚如何筛选是否进行执行、签名
1、对长期、临时、当天医嘱、有效长期医嘱进行分类(按时间排序)
获取个人医嘱的所有分类 ,进入医嘱筛选分类器
2、在一段时间间隔内,对多人能够进行长期 临时 进行筛选(加入时间、和病人个人)
获取个人缓存的时间,进行长期或者临时筛选
3、在指定的日期内进行查找所有的数据,并按要求字段进行筛选显示,获取指定日期, 进入分类筛选器操作 。
- 共性,都共用一个数据源,并在此基础上进行筛选操作
- 构造三个不同的分类器,对源数据进行分类
缓存清楚操作,三个业务
与以往对比:
能够从大批量中查找时间+分类时间
主要是分类的时间
查看每个人的数据
查询数据: 对数据进行
数据清洗
分类器:传入,构造分类规则, 分类器中进行筛选
回写数据:
L 临时 c 长期
1 | /** |
医嘱停止:
1 | USE [pyyzyy] |
医嘱停止sql
1 |
|
测试样例
1 | Test .junit. |
修改两个时间,并将状态设置未 T
三测单回写
采写时间回写:
1 | USE [pyyzyy] |
双签名回写
存储过程
1 |
|
存储过程检验
1 | /** |
未签名之前
测试结果
更新失败:
当医嘱不是短期医嘱
1 | Test .junit. |
更新成功:
医嘱执行
1 | USE [pyyzyy] |
短期医嘱执行:
1 | select top 100 a.D_ZDBH, a.住院号,D_ZXQM, D_ZXSJ,b.ID,D_LCBZ |
长期医嘱执行
没有修改之前
查询sql
1 |
|
修改之后:
1 |
|
三测单回写
1 | USE [pyyzyy] |
新增
三测单查询视图:
1 | SELECT b.姓名,a.D_ZDBH as 账单编码, b.住院号, b.住院次数, b.收费类别, b.性别, b.年龄, b.入院科别代码, b.入院科别, b.疾病分类, c.Name AS 主治医生, |
三测单VO
1 | package medical.appsupport.vo; |
医嘱视图:
1 | SELECT a.D_ZDBH AS PatientID, b.ID AS VISIT_ID, a.住院号 AS INP_NO, '' AS BABY_ID, b.D_HM AS ORDER_NO, b.D_PYH AS ORDER_NO_SUB, b.D_CDBH AS ORDER_CODE, b.D_CDMC AS ORDER_TEXT, |
皮试结果:
1 | USE [pyyzyy] |
病人查询页面:
页面填写查询信息
查询相应的信息:
修改查询执行页面,输入需要查询的信息
输液扫描:
使用姓名获取当前的医嘱
扫码查询医嘱:
扫描瓶签
更新瓶签数据
更新定时任务
修改扫描关注信息
1 | { |
使用视图进行查询
1 | /** |