尚医通笔记(含bug修改方法)

2023-10-17 01:20

本文主要是介绍尚医通笔记(含bug修改方法),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

项目简介:

包含系统

项目架构​

前端开发流程:​

common模块

swagger2

Result(全局统一返回结果)

YyghException(自定义全局异常)

GlobalExceptionHandler(全局异常处理器)

JwtHelper(生成Token、根据Token获取用户信息)

AuthContextHolder(获取用户信息)

HttpRequestHelper

MD5加密

HttpUtil

model模块

 BaseEntity

BaseMongoEntity

service_cmn(数据字典接口)

easyexcel(导入导出字典)

listener

树形列表

spring Cache + redis 缓存数据

service_hosp(医院api接口)

MybatisPlus

Mongodb

部门查询

nacos

JWT

登录功能

手机号登录

微信登录

微信支付

退款

阿里OSS

RabbitMQ

定时任务

ECharts统计

Bug


项目简介:

包含系统

预约挂号后台管理系统

前台用户系统就是114挂号网站

114网上预约挂号 - 北京市预约挂号统一平台

医院接口系统:

项目架构

前端开发流程:

约定 > 配置 > 编码,项目父工程中规定所有共用依赖的版本。

common模块

将全局要使用的实体类和工具放到此模块中,避免代码冗余

swagger2

swagger通过注解表明该接口会生成文档,包括接口名、请求方法、参数、返回信息的等等。

使用swagger要完成以下三部

  1. @Api:修饰整个类,描述Controller的作用

  2. @ApiOperation:描述一个类的一个方法,或者说一个接口

  3. @ApiParam:单个参数描述

  4. @ApiModel:用对象来接收参数

  5. @ApiModelProperty:用对象接收参数时,描述对象的一个字段

  6. @ApiImplicitParam:一个请求参数

  7. @ApiImplicitParams:多个请求参数

1、导入pom依赖

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

2、配置拦截路径

/*** Swagger2配置信息*/
@Configuration
@EnableSwagger2
public class Swagger2Config {@Beanpublic Docket webApiConfig(){return new Docket(DocumentationType.SWAGGER_2).groupName("webApi").apiInfo(webApiInfo()).select()//只显示api路径下的页面.paths(Predicates.and(PathSelectors.regex("/api/.*"))).build();}@Beanpublic 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("linxi", "http://linxi.com", "2738328047@qq.com")).build();}private ApiInfo adminApiInfo(){return new ApiInfoBuilder().title("后台管理系统-API文档").description("本文档描述了后台管理系统微服务接口定义").version("1.0").contact(new Contact("linxi", "http://linxi.com", "2738328047@qq.com")).build();}
}

3、在主启动上添加注解

//扫描swagger的包
@ComponentScan(basePackages = "com.linxi")

Result(全局统一返回结果)

将所有请求映射返回的信息封装在Result中,泛型为任意类型。 Result.ok()返回前端code为200,Result.fail()返回前端code为201,当然这里面可以添数据,Result.ok(map)返回一个 map集合,配合枚举类使用更方便。

Result类

/*** 全局统一返回结果类*/
@Data
@ApiModel(value = "全局统一返回结果")
public class Result<T> {@ApiModelProperty(value = "返回码")private Integer code;@ApiModelProperty(value = "返回消息")private String message;@ApiModelProperty(value = "返回数据")private T data;public Result(){}protected static <T> Result<T> build(T data) {Result<T> result = new Result<T>();if (data != null)result.setData(data);return result;}public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {Result<T> result = build(body);result.setCode(resultCodeEnum.getCode());result.setMessage(resultCodeEnum.getMessage());return result;}public static <T> Result<T> build(Integer code, String message) {Result<T> result = build(null);result.setCode(code);result.setMessage(message);return result;}public static<T> Result<T> ok(){return Result.ok(null);}/*** 操作成功* @param data* @param <T>* @return*/public static<T> Result<T> ok(T data){Result<T> result = build(data);return build(data, ResultCodeEnum.SUCCESS);}public static<T> Result<T> fail(){return Result.fail(null);}/*** 操作失败* @param data* @param <T>* @return*/public static<T> Result<T> fail(T data){Result<T> result = build(data);return build(data, ResultCodeEnum.FAIL);}public Result<T> message(String msg){this.setMessage(msg);return this;}public Result<T> code(Integer code){this.setCode(code);return this;}public boolean isOk() {if(this.getCode().intValue() == ResultCodeEnum.SUCCESS.getCode().intValue()) {return true;}return false;}
}

枚举类

/*** 统一返回结果状态信息类*/
@Getter
public enum ResultCodeEnum {SUCCESS(200,"成功"),FAIL(201, "失败"),PARAM_ERROR( 202, "参数不正确"),SERVICE_ERROR(203, "服务异常"),DATA_ERROR(204, "数据异常"),DATA_UPDATE_ERROR(205, "数据版本异常"),LOGIN_AUTH(208, "未登陆"),PERMISSION(209, "没有权限"),CODE_ERROR(210, "验证码错误"),
//    LOGIN_MOBLE_ERROR(211, "账号不正确"),LOGIN_DISABLED_ERROR(212, "该用户已被禁用"),REGISTER_MOBLE_ERROR(213, "手机号已被使用"),LOGIN_AURH(214, "需要登录"),LOGIN_ACL(215, "没有权限"),URL_ENCODE_ERROR( 216, "URL编码失败"),ILLEGAL_CALLBACK_REQUEST_ERROR( 217, "非法回调请求"),FETCH_ACCESSTOKEN_FAILD( 218, "获取accessToken失败"),FETCH_USERINFO_ERROR( 219, "获取用户信息失败"),DEPARTMENT_DELETE_FAIL(221,"科室不存在"),//LOGIN_ERROR( 23005, "登录失败"),PAY_RUN(220, "支付中"),CANCEL_ORDER_FAIL(225, "取消订单失败"),CANCEL_ORDER_NO(225, "不能取消预约"),HOSCODE_EXIST(230, "医院编号已经存在"),NUMBER_NO(240, "可预约号不足"),TIME_NO(250, "当前时间不可以预约"),SIGN_ERROR(300, "签名错误"),HOSPITAL_OPEN(310, "医院未开通,暂时不能访问"),HOSPITAL_LOCK(320, "医院被锁定,暂时不能访问"),;private Integer code;private String message;private ResultCodeEnum(Integer code, String message) {this.code = code;this.message = message;}
}

YyghException(自定义全局异常)

@Data
@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();}@Overridepublic String toString() {return "YyghException{" +"code=" + code +", message=" + this.getMessage() +'}';}
}

GlobalExceptionHandler(全局异常处理器)

@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(Exception.class)@ResponseBodypublic Result error(Exception e) {e.printStackTrace();return Result.fail();}@ExceptionHandler(YyghException.class)@ResponseBodypublic Result error(YyghException e) {e.printStackTrace();return Result.fail();}
}

JwtHelper(生成Token、根据Token获取用户信息)

        <dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId></dependency>
public class JwtHelper {//Token过期时间(ms)private static long tokenExpiration = 24*60*60*1000;//Token签名密钥private static String tokenSignKey = "linxi";/***根据参数生成Token*/public static String createToken(Long userId, String userName) {String token = Jwts.builder().setSubject("YYGH-USER").setExpiration(new Date(System.currentTimeMillis() + tokenExpiration)).claim("userId", userId).claim("userName", userName).signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();return token;}/***根据Token得到用户id*/public static Long getUserId(String token) {if(StringUtils.isEmpty(token)) return null;Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);Claims claims = claimsJws.getBody();Integer userId = (Integer)claims.get("userId");return userId.longValue();}/***根据Token得到用户名称*/public static String getUserName(String token) {if(StringUtils.isEmpty(token)) return "";Jws<Claims> claimsJws= Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);Claims claims = claimsJws.getBody();return (String)claims.get("userName");}//测试方法public static void main(String[] args) {String token = JwtHelper.createToken(1L, "linxi");System.out.println(token);System.out.println(JwtHelper.getUserId(token));System.out.println(JwtHelper.getUserName(token));}
}

AuthContextHolder(获取用户信息)

AuthContextHolder类封装了JwtHelper中的方法,使得业务分离。全局根据Token获取信息调用这个的方法,而生成Token使用JwtHelper中的方法createToken。

/*** 获取当前用户信息的工具类*/
public class AuthContextHolder {/***     获取用户id*/public static Long getUserId(HttpServletRequest request){//获取用户tokenString token = request.getHeader("token");//jwt从token中获取userIdLong userId = JwtHelper.getUserId(token);return userId;}/*** 获取用户名称*/public static String getUserName(HttpServletRequest request){//获取用户tokenString token = request.getHeader("token");//jwt从token中获取userIdString userName = JwtHelper.getUserName(token);return userName;}}

HttpRequestHelper

@Slf4j
public class HttpRequestHelper {public static void main(String[] args) {Map<String, Object> paramMap = new HashMap<>();paramMap.put("d", "4");paramMap.put("b", "2");paramMap.put("c", "3");paramMap.put("a", "1");paramMap.put("timestamp", getTimestamp());log.info(getSign(paramMap, "111111111"));}/**** @param paramMap* @return*/public static Map<String, Object> switchMap(Map<String, String[]> paramMap) {Map<String, Object> resultMap = new HashMap<>();for (Map.Entry<String, String[]> param : paramMap.entrySet()) {resultMap.put(param.getKey(), param.getValue()[0]);}return resultMap;}/*** 请求数据获取签名* @param paramMap* @param signKey* @return*/public static String getSign(Map<String, Object> paramMap, String signKey) {if(paramMap.containsKey("sign")) {paramMap.remove("sign");}TreeMap<String, Object> sorted = new TreeMap<>(paramMap);StringBuilder str = new StringBuilder();//        for (Map.Entry<String, Object> param : sorted.entrySet()) {
//            str.append(param.getValue()).append("|");
//        }str.append(signKey);log.info("加密前:" + str.toString());String md5Str = MD5.encrypt(str.toString());log.info("加密后:" + md5Str);return md5Str;}/*** 签名校验* @param paramMap* @param signKey* @return*/public static boolean isSignEquals(Map<String, Object> paramMap, String signKey) {String sign = (String)paramMap.get("sign");String md5Str = getSign(paramMap, signKey);if(!sign.equals(md5Str)) {return false;}return true;}/*** 获取时间戳* @return*/public static long getTimestamp() {return new Date().getTime();}/*** 封装同步请求* @param paramMap* @param url* @return*/public static JSONObject sendRequest(Map<String, Object> paramMap, String url){String result = "";try {//封装post参数StringBuilder postdata = new StringBuilder();for (Map.Entry<String, Object> param : paramMap.entrySet()) {postdata.append(param.getKey()).append("=").append(param.getValue()).append("&");}log.info(String.format("--> 发送请求:post data %1s", postdata));byte[] reqData = postdata.toString().getBytes("utf-8");//调用HttpUtilbyte[] respdata = HttpUtil.doPost(url,reqData);result = new String(respdata);log.info(String.format("--> 应答结果:result data %1s", result));} catch (Exception ex) {ex.printStackTrace();}return JSONObject.parseObject(result);}
}

MD5加密

/*** MD5加密*/
public final class MD5 {public static String encrypt(String strSrc) {try {char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8','9', 'a', 'b', 'c', 'd', 'e', 'f' };byte[] bytes = strSrc.getBytes();MessageDigest md = MessageDigest.getInstance("MD5");md.update(bytes);bytes = md.digest();int j = bytes.length;char[] chars = new char[j * 2];int k = 0;for (int i = 0; i < bytes.length; i++) {byte b = bytes[i];chars[k++] = hexChars[b >>> 4 & 0xf];chars[k++] = hexChars[b & 0xf];}return new String(chars);} catch (NoSuchAlgorithmException e) {e.printStackTrace();throw new RuntimeException("MD5加密出错!!+" + e);}}
}

HttpUtil

@Slf4j
public final class HttpUtil {static final String POST = "POST";static final String GET = "GET";static final int CONN_TIMEOUT = 30000;// msstatic final int READ_TIMEOUT = 30000;// ms/*** post 方式发送http请求.* * @param strUrl* @param reqData* @return*/public static byte[] doPost(String strUrl, byte[] reqData) {return send(strUrl, POST, reqData);}/*** get方式发送http请求.* * @param strUrl* @return*/public static byte[] doGet(String strUrl) {return send(strUrl, GET, null);}/*** @param strUrl* @param reqmethod* @param reqData* @return*/public static byte[] send(String strUrl, String reqmethod, byte[] reqData) {try {URL url = new URL(strUrl);HttpURLConnection httpcon = (HttpURLConnection) url.openConnection();httpcon.setDoOutput(true);httpcon.setDoInput(true);httpcon.setUseCaches(false);httpcon.setInstanceFollowRedirects(true);httpcon.setConnectTimeout(CONN_TIMEOUT);httpcon.setReadTimeout(READ_TIMEOUT);httpcon.setRequestMethod(reqmethod);httpcon.connect();if (reqmethod.equalsIgnoreCase(POST)) {OutputStream os = httpcon.getOutputStream();os.write(reqData);os.flush();os.close();}BufferedReader in = new BufferedReader(new InputStreamReader(httpcon.getInputStream(),"utf-8"));String inputLine;StringBuilder bankXmlBuffer = new StringBuilder();while ((inputLine = in.readLine()) != null) {  bankXmlBuffer.append(inputLine);  }  in.close();  httpcon.disconnect();return bankXmlBuffer.toString().getBytes();} catch (Exception ex) {log.error(ex.toString(), ex);return null;}}/*** 从输入流中读取数据* * @param inStream* @return* @throws Exception*/public static byte[] readInputStream(InputStream inStream) throws Exception {ByteArrayOutputStream outStream = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int len = 0;while ((len = inStream.read(buffer)) != -1) {outStream.write(buffer, 0, len);}byte[] data = outStream.toByteArray();// 网页的二进制数据outStream.close();inStream.close();return data;}
}

model模块

定义了所有共用的枚举类和实体类(表数据)封装了所有表连接查询类(将不同表中的部分数据封装在一起作为查询字段)。

 BaseEntity

所有关于mysql表的实体类继承 BaseEntity ,他们都有这些共同的字段。最后的map集合是封装其它数据返回给前端的,数据库中不存在该字段,因此@TableField(exist = false)。

@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@TableField("is_deleted")private Integer isDeleted;@ApiModelProperty(value = "其他参数")@TableField(exist = false)private Map<String,Object> param = new HashMap<>();
}

BaseMongoEntity

与BaseEntity同功能,但针对于mongodb, @Transien表示不录入到数据库中。

@Data
public class BaseMongoEntity implements Serializable {@ApiModelProperty(value = "id")@Idprivate String id;@ApiModelProperty(value = "创建时间")private Date createTime;@ApiModelProperty(value = "更新时间")private Date updateTime;@ApiModelProperty(value = "逻辑删除(1:已删除,0:未删除)")private Integer isDeleted;@ApiModelProperty(value = "其他参数")@Transient //被该注解标注的,将不会被录入到数据库中。只作为普通的javaBean属性private Map<String,Object> param = new HashMap<>();
}

service

包含以下api接口服务

service_cmn(数据字典接口)

数据字典中包全国省市区、医院等级、证件类型、民族、学历。

表中idparent_id相对应,dict_code和id建立联系,value表示数据对应的值或者说用该值代表数据,所有数据存在一张表中,避免连表查询(笛卡尔积)

easyexcel(导入导出字典)

导入导出数据字典理应excel文件,需要引入依赖

pom

        <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.2.10</version></dependency>
    //导入数据字典接口@PostMapping("importData")public Result importDict(MultipartFile file){dictService.importDictData(file);return Result.ok();}//导出数据字典接口@GetMapping("exportData")public void exportDict(HttpServletResponse response){dictService.exportDictData(response);}
    //导出数据字典接口@Overridepublic void exportDictData(HttpServletResponse response) {response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");//这里的 URLEncoder.encode 可以防止中文乱码,与easyExcel无关系String fileName = "dict";//已下载形式response.setHeader("Content-disposition", "attachment;fileName" + fileName + ".xlsx");//查询寻数据库List<Dict> dictList = baseMapper.selectList(null);//将dict转换成dictEoVoList<DictEeVo> dictVoList = new ArrayList<>();for (Dict dict : dictList) {DictEeVo dictEeVo = new DictEeVo();BeanUtils.copyProperties(dict, dictEeVo);dictVoList.add(dictEeVo);}//调用方法实现写操作try {EasyExcel.write(response.getOutputStream(), DictEeVo.class).sheet("dict").doWrite(dictVoList);} catch (IOException e) {e.printStackTrace();}}//导入数据字典@Override@CacheEvict(value = "dict", allEntries = true)//清空所有缓存public void importDictData(MultipartFile file) {try {EasyExcel.read(file.getInputStream(), DictEeVo.class, new DictDataListener(baseMapper)).sheet().doRead();} catch (IOException e) {e.printStackTrace();}}

listener

在读取excel表格数据时需要监听器来封装读取操作。

public class DictDataListener extends AnalysisEventListener<DictEeVo> {private DictMapper dictMapper;public DictDataListener(DictMapper dictMapper) {this.dictMapper = dictMapper;}//一行一行读取数据@Overridepublic void invoke(DictEeVo dictEeVo, AnalysisContext analysisContext) {Dict dict = new Dict();//数据转换BeanUtils.copyProperties(dictEeVo,dict);dictMapper.insert(dict);}@Overridepublic void doAfterAllAnalysed(AnalysisContext analysisContext) {}
}

树形列表

当我们点击任意节点,会判断是否存在子节点,有则会显示。

根据上表的数据库字段,可以先根据 dict_code 查出 id,再通过 id 和 parent_id 的关系依次查出,也可以直接使用 id ,具体看前端传数据。

    //根据数据id查询子数据列表@Override@Cacheable(value = "dict", keyGenerator = "keyGenerator")//第一次查询后将数据放入缓存中public List<Dict> findChildData(Long id) {QueryWrapper<Dict> queryWrapper = new QueryWrapper<>();queryWrapper.eq("parent_id", id);List<Dict> dictList = baseMapper.selectList(queryWrapper);//循环得到每个对象for (Dict dict : dictList) {Long dictId = dict.getId();//根据id判断下面是否有子节点boolean haschild = baseMapper.selectCount(new QueryWrapper<Dict>().eq("parent_id", dictId)) > 0;dict.setHasChildren(haschild);}return dictList;}

spring Cache + redis 缓存数据

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>

增加redis配置类

@EnableCaching 开启缓存

@Configuration
@EnableCaching
public 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;}
}
#redis
spring.redis.host=192.168.*.*
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0

配置完成后在查询方法上增加注解就行,这个方法在上面的树形列表中

@Cacheable(value = "dict", keyGenerator = "keyGenerator")//第一次查询后将数据放入缓存中

service_hosp(医院api接口)

结构:api包下接口提供给前台用户系统使用,其余接口提供给后台管理系统使用。

医院的基础信息设置是在mysql中(由后台管理系统crud)

医院的详细信息在Mongodb中(由医院接口系统crud)

MybatisPlus

项目中所有的MybatisPlus的使用类都继承于 IService 、ServiceImpl。就可以直接使用它们的方法,如下面的分页查询(记得配置分页插件)。

@Mapper
public interface HospitalSetMapper extends BaseMapper<HospitalSet> {
}
public interface HospitalSetService extends IService<HospitalSet> {}
@Service
public class HospitalSetServiceImpl extends ServiceImpl<HospitalSetMapper, HospitalSet> implements HospitalSetService {}

这里面调用的page方法就是Iservice里面的,并没有在hospitalSetService定义(注意Page导的时mybatisPlus的包),当然还有其他的方法:list、save、update、getById、updateById、removeByIds

    //条件查询带分页@ApiOperation(value = "条件查询带分页")@PostMapping("findPageHospSet/{current}/{limit}")public Result findPageHospSet(@PathVariable("current") Long current,@PathVariable("limit") Long limit,//通过json传入数据,可以为空@RequestBody(required = false) HospitalSetQueryVo hospitalSetQueryVo) {//当前页、每页记录数Page<HospitalSet> page = new Page<>(current, limit);//构造条件QueryWrapper<HospitalSet> queryWrapper = new QueryWrapper<>();String hosname = hospitalSetQueryVo.getHosname();String hoscode = hospitalSetQueryVo.getHoscode();if (!StringUtils.isEmpty(hosname)) {//医院名称模糊查询queryWrapper.like("hosname", hosname);}if (!StringUtils.isEmpty(hoscode)) {//匹配医院编号queryWrapper.eq("hoscode", hoscode);}Page<HospitalSet> queryPage = hospitalSetService.page(page, queryWrapper);return Result.ok(queryPage);}

Mongodb

mongodb使用分两种 MongoTemplate 、MongoRepository

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

下面使用MongoRepository

@Repository
public interface HospitalRepository extends MongoRepository<Hospital,String> {//判断是否存在数据Hospital getHospitalByHoscode(String hoscode);//根据医院名称查询List<Hospital> findHospitalByHosnameLike(String hosname);
}

有意思的是:只需要在继承了MongoRepository中书写方法体 MongoRepository就会帮我们自动实现这个方法,非常的简便。Spring Data 提供了对mongodb数据访问我们只需要继承MongoRepository类,按照Spring Data规范就可以。

在使用时先将 定义HospitalRepository 注入,然后调用MongoRepository方法即可,或者根据业务需要按照springData规范自定义方法列如:getHospitalByHoscode(hoscode)、

findScheduleByHoscodeAndDepcodeAndWorkDate(...)。
    //医院查询(条件查询带分页)@Overridepublic Page<Hospital> selectHospPage(int page, int limit, HospitalQueryVo hospitalQueryVo) {Hospital hospital = new Hospital();BeanUtils.copyProperties(hospitalQueryVo, hospital);ExampleMatcher matcher = ExampleMatcher.matching().withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING) //模糊查询.withIgnoreCase(true);//忽略大小写Example<Hospital> example = Example.of(hospital, matcher);Pageable pageable = PageRequest.of(page - 1, limit);Page<Hospital> pages = hospitalRepository.findAll(example, pageable);//获取查询list集合,遍历进行医院等级封装pages.getContent().stream().forEach(item -> {this.setHospitalHosType(item);});return pages;}//获取查询list集合,遍历进行医院等级封装private Hospital setHospitalHosType(Hospital hospital) {//更具dictCode和value获取医院名称String hostypeString = dictFeignClient.getName("hostype", hospital.getHostype());//查询省市区String provinceString = dictFeignClient.getName(hospital.getProvinceCode());String cityString = dictFeignClient.getName(hospital.getCityCode());String districtString = dictFeignClient.getName(hospital.getDistrictCode());hospital.getParam().put("fullAddress", provinceString + cityString + districtString);hospital.getParam().put("hostypeString", hostypeString);return hospital;}

下面使用mongoTemplate 进行所有的排班查询

注入bean必不可少

    @Autowiredprivate MongoTemplate mongoTemplate;

然后就是实现方法

    //查询排班规则数据@Overridepublic Map<String, Object> getScheduleRule(int page, int limit, String hoscode, String depcode) {//1、根据医院编号和科室编号进行查询Criteria criteria = Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode);//2、根据工作日期workDate进行分组Aggregation agg = Aggregation.newAggregation(Aggregation.match(criteria),//匹配条件Aggregation.group("workDate")//分组字段.first("workDate").as("workDate")//3、统计号源数量(求和).count().as("docCount").sum("reservedNumber").as("reservedNumber").sum("availableNumber").as("availableNumber"),//排序Aggregation.sort(Sort.Direction.DESC, "workDate"),//4、实现分页Aggregation.skip((page - 1) * limit),Aggregation.limit(limit));//调用方法,最后执行AggregationResults<BookingScheduleRuleVo> aggResults =mongoTemplate.aggregate(agg, Schedule.class, BookingScheduleRuleVo.class);List<BookingScheduleRuleVo> bookingScheduleRuleVoList = aggResults.getMappedResults();//分组查询总记录数Aggregation totalAgg = Aggregation.newAggregation(Aggregation.match(criteria),Aggregation.group("workDate") //通过工作日期进行分组);//调用方法查询AggregationResults<BookingScheduleRuleVo> totalAggResult =mongoTemplate.aggregate(totalAgg, Schedule.class, BookingScheduleRuleVo.class);int total = totalAggResult.getMappedResults().size(); //某天的总记录数//根据日期获取星期for (BookingScheduleRuleVo bookingScheduleRuleVo : bookingScheduleRuleVoList) {//获取日期Date workDate = bookingScheduleRuleVo.getWorkDate();//getDayOfWeek 自定义的方法,利用String dayOfWeek = this.getDayOfWeek(new DateTime(workDate));bookingScheduleRuleVo.setDayOfWeek(dayOfWeek);}//设置最终数据进行返回Map<String, Object> resultMap = new HashMap<>();resultMap.put("bookingScheduleRuleList",bookingScheduleRuleVoList);resultMap.put("total",total);//获取医院名称String hosName = hospitalService.getHosName(hoscode);Map<String, Object> baseMap = new HashMap<>();baseMap.put("hosname",hosName);resultMap.put("baseMap",baseMap);return resultMap;}

部门查询

做成树性列表,大科室包含很多小科室。配合上面查出的排版信息可做成下列画面

这个功能最难的代码是:查询出的所有部门信息是一个list集合,如何将它们进行分组,代码如下

        //根据大科室 bigcode分组,获取每个大科室的所有子科室Map<String, List<Department>> departmentMap =departmentList.stream().collect(Collectors.groupingBy(Department::getBigcode));

分组后要封装所有大小科室的信息,

      //遍历map集合:通过key和value的关系entryfor (Map.Entry<String, List<Department>> entry : departmentMap.entrySet()){//大科室编号String bigcode = entry.getKey();//大科室编号对应的全部数据List<Department> departments = entry.getValue();/*封装大科室*/DepartmentVo departmentVo = new DepartmentVo();departmentVo.setDepcode(bigcode); //设置大科室编号departmentVo.setDepname(departments.get(0).getBigname());//设置大科室名称/*封装小科室*/List<DepartmentVo> children = new ArrayList<>();//遍历得到每个小科室for (Department department : departments){DepartmentVo departmentVo1 = new DepartmentVo();departmentVo1.setDepcode(department.getDepcode());//设置小科室编号departmentVo1.setDepname(department.getDepname());//设置小科室名称children.add(departmentVo1);}//把小科室放到对应大科室的children去departmentVo.setChildren(children);//最终放到result去返回result.add(departmentVo);}

 最终返回list集合给前端。它是这样的结构:List<Map<String, List<Department>>>

 

前端传递的json数据经过HttpRequestHelper处理后是map的json串,将他装成对象使JSONObject

    public void save(Map<String, Object> paraMap) {//将json转换成对象String s = JSONObject.toJSONString(paraMap);Department department = JSONObject.parseObject(s, Department.class);}

nacos

JWT

手机号登录

微信登录

微信支付

退款

阿里OSS

pom

        <dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId></dependency><!-- 日期工具栏依赖 --><dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId></dependency>

配置文件

aliyun.oss.endpoint=***********************
aliyun.oss.accessKeyId=**********
aliyun.oss.secret=**********************
aliyun.oss.bucket=***************

controller的方法参数使用的是MultipartFile,post请求,

具体实现方法:

  public String upload(MultipartFile file) {String endpoint = ConstantOssPropertiesUtils.ENDPOINT;String accessKeyId = ConstantOssPropertiesUtils.ACCESS_KEY_ID;String accessKeySecret = ConstantOssPropertiesUtils.SECRECT;String bucketName = ConstantOssPropertiesUtils.BUCKET;//保证文件名唯一String fileName = UUID.randomUUID().toString().substring(0,16).replaceAll("-","")+file.getOriginalFilename();//按照当前日期创建文件夹,放入当日上传的文件(便于查改)//  /2022/02/28/ xxx.jpgString timeUrl = new DateTime().toString("yyyy/MM/dd");fileName = timeUrl + "/" + fileName;try {// 创建OSSClient实例。OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);//获取文件流InputStream inputStream = file.getInputStream();//调用方法实现上传ossClient.putObject(bucketName, fileName, inputStream);//关闭实例if (ossClient != null) {ossClient.shutdown();}//返回文件路径String url = "https://"+bucketName+"."+endpoint+"/"+fileName;return url;} catch (Exception e) {e.printStackTrace();return null;}}

RabbitMQ

pom

        <!--rabbitmq消息队列--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bus-amqp</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId></dependency>

将RabbitMQ放到公共类中,便于后面多个模块使用。这里定义了项目所需的队列交换机和路由。

 在配置类中配置消息转换器

/*** mq消息转换器* 默认是字符串转换器*/
@Configuration
public class MQConfig {@Beanpublic MessageConverter messageConverter(){return new Jackson2JsonMessageConverter();}
}

编写sendMessage方法,便于后面所有模块的调用。

@Service
public class RabbitService {@Autowiredprivate RabbitTemplate rabbitTemplate;/***  发送消息* @param exchange 交换机* @param routingKey 路由键* @param message 消息*/public boolean sendMessage(String exchange, String routingKey, Object message) {rabbitTemplate.convertAndSend(exchange, routingKey, message);return true;}
}

定时任务

其实就是利用了两个注解,cron 表达式 定时发送信息(task)给信息队列,另一服务端监听到(task)并实写提醒方法,筛选提醒人群,发送消息(msm短信)传递参数到rabbit,由msm模块监听(msm)后调用业务类实现提醒短信发送。

@Component
@EnableScheduling
public class ScheduledTask {@Autowiredprivate RabbitService rabbitService;//每天8点执行提醒//cron 表达式,设置时间间隔(0 0 8 * * ?)@Scheduled(cron = "0/30 * * * * ?") //为了测试实际使用public void task1() {rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_TASK, MqConst.ROUTING_TASK_8, "");}
}

之后的方法就不一 一阐述了。

ECharts统计

选用ECharts实现图表折线类统计图

 

采用服务调用(需要配置网关),Statistics 调用 order ,具体方法实现在order,

Bug

1、mongodb 8小时时间差问题:在有关时间的字段上添加注解:

@JsonFormat(pattern = "yyyy-MM-dd", timezone="GMT+8")

或者在配置文件中添加:

#返回json的全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

2、在远程服务调用时,不加“{ }”总是注入不了bean,莫名出错,可能是版本问题

@EnableFeignClients(basePackages = {"com.linxi"})

3、前端myheader中微信登陆回调openid判断由 "" 改成!=null,后端传过来的' '将无法被识别导致你每次微信扫码登录都需要手机号注册

4、将OrderInfo实体类中scheduleId的@TableField的参数改成与数据库一致的hos_schedule_id,

5、将getSign加密方法后面的for循环加密参数注掉,否者签名容易为null,manage和order都要注掉

6、修改ApiServiceImpl类中saveHospital方法paramMap.put("sign",MD5.encrypt(this.getSignKey()));加密方式为MD5加密,保证mysql和mongodb的签名一致

7、微信退款请求微信api报SSL协议错误 ,因为:微信服务端更新取消TLSv1协议。修改工具类HttpClient的execute方法,使用 

 SSLConnectionSocketFactory sslsf =new SSLConnectionSocketFactory(sslContext,new DefaultHostnameVerifier());

8、项目无法打包,无法找到该包,但是在业务类中导包和使用都是正确的,解决方案:

添加xml在pom中,先将父工程打包,再打包common类、model类等公共类,最后再打包业务类

    <build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><classifier>execute</classifier></configuration></plugin></plugins></build>

9、部分业务类无法启动,报无法连接redis,但是pom和配置文件中并没有有关redis的引入与设置,也没有使用redis缓存,于是我配置了本地虚拟机的redis,但是他尝试连接的IP和我输入的IP不一致,很神奇的bug,好像我是重启idea再将项目重上到下打包好几遍最终它又消失了

10、微信退款证书过失,因为老师给的mysql数据和mongodb数据都是写的之前的时间,而我们在查询排班时有需要获取本地时间(本地时间排班无数据),mysql中的所有时间数据修改较容易但是mongodb中的修改较复杂(太多了),最简单的方法就是修改本地时间,可以查出之前的排班数据,但是微信退款也会获取本地时间导致证书过期,我妥协了只好当要退款时又将时间修改回来即可

这篇关于尚医通笔记(含bug修改方法)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/221995

相关文章

vue基于ElementUI动态设置表格高度的3种方法

《vue基于ElementUI动态设置表格高度的3种方法》ElementUI+vue动态设置表格高度的几种方法,抛砖引玉,还有其它方法动态设置表格高度,大家可以开动脑筋... 方法一、css + js的形式这个方法需要在表格外层设置一个div,原理是将表格的高度设置成外层div的高度,所以外层的div需要

Python判断for循环最后一次的6种方法

《Python判断for循环最后一次的6种方法》在Python中,通常我们不会直接判断for循环是否正在执行最后一次迭代,因为Python的for循环是基于可迭代对象的,它不知道也不关心迭代的内部状态... 目录1.使用enuhttp://www.chinasem.cnmerate()和len()来判断for

Java循环创建对象内存溢出的解决方法

《Java循环创建对象内存溢出的解决方法》在Java中,如果在循环中不当地创建大量对象而不及时释放内存,很容易导致内存溢出(OutOfMemoryError),所以本文给大家介绍了Java循环创建对象... 目录问题1. 解决方案2. 示例代码2.1 原始版本(可能导致内存溢出)2.2 修改后的版本问题在

四种Flutter子页面向父组件传递数据的方法介绍

《四种Flutter子页面向父组件传递数据的方法介绍》在Flutter中,如果父组件需要调用子组件的方法,可以通过常用的四种方式实现,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录方法 1:使用 GlobalKey 和 State 调用子组件方法方法 2:通过回调函数(Callb

一文详解Python中数据清洗与处理的常用方法

《一文详解Python中数据清洗与处理的常用方法》在数据处理与分析过程中,缺失值、重复值、异常值等问题是常见的挑战,本文总结了多种数据清洗与处理方法,文中的示例代码简洁易懂,有需要的小伙伴可以参考下... 目录缺失值处理重复值处理异常值处理数据类型转换文本清洗数据分组统计数据分箱数据标准化在数据处理与分析过

Java中Object类的常用方法小结

《Java中Object类的常用方法小结》JavaObject类是所有类的父类,位于java.lang包中,本文为大家整理了一些Object类的常用方法,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. public boolean equals(Object obj)2. public int ha

golang1.23版本之前 Timer Reset方法无法正确使用

《golang1.23版本之前TimerReset方法无法正确使用》在Go1.23之前,使用`time.Reset`函数时需要先调用`Stop`并明确从timer的channel中抽取出东西,以避... 目录golang1.23 之前 Reset ​到底有什么问题golang1.23 之前到底应该如何正确的

Vue项目中Element UI组件未注册的问题原因及解决方法

《Vue项目中ElementUI组件未注册的问题原因及解决方法》在Vue项目中使用ElementUI组件库时,开发者可能会遇到一些常见问题,例如组件未正确注册导致的警告或错误,本文将详细探讨这些问题... 目录引言一、问题背景1.1 错误信息分析1.2 问题原因二、解决方法2.1 全局引入 Element

Python调用另一个py文件并传递参数常见的方法及其应用场景

《Python调用另一个py文件并传递参数常见的方法及其应用场景》:本文主要介绍在Python中调用另一个py文件并传递参数的几种常见方法,包括使用import语句、exec函数、subproce... 目录前言1. 使用import语句1.1 基本用法1.2 导入特定函数1.3 处理文件路径2. 使用ex

Oracle查询优化之高效实现仅查询前10条记录的方法与实践

《Oracle查询优化之高效实现仅查询前10条记录的方法与实践》:本文主要介绍Oracle查询优化之高效实现仅查询前10条记录的相关资料,包括使用ROWNUM、ROW_NUMBER()函数、FET... 目录1. 使用 ROWNUM 查询2. 使用 ROW_NUMBER() 函数3. 使用 FETCH FI