本文主要是介绍尚医通笔记(含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要完成以下三部
-
@Api:修饰整个类,描述Controller的作用
-
@ApiOperation:描述一个类的一个方法,或者说一个接口
-
@ApiParam:单个参数描述
-
@ApiModel:用对象来接收参数
-
@ApiModelProperty:用对象接收参数时,描述对象的一个字段
-
@ApiImplicitParam:一个请求参数
-
@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(数据字典接口)
数据字典中包全国省市区、医院等级、证件类型、民族、学历。
表中id与parent_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修改方法)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!