Create your Gitee Account
Explore and code with more than 6 million developers,Free private repositories !:)
Sign up
This repository doesn't specify license. Without author's permission, this code is only for learning and cannot be used for other purposes.
Clone or download
Cancel
Notice: Creating folder will generate an empty file .keep, because not support in Git
Loading...
README.md

框架规范


通用规范
  1. 接口文档Swagger
  2. 统一返回响应ResponseResult/ResponseCode/Page<Object>,务必注意泛型设置具体类【ResponseResult<List<Patient>>】,杜绝直接返回无泛型ResponseResult否则Swagger无法展示具体属性
  3. Rest API命名杜绝含有大写,杜绝类似/getDoctorAppraisePageByDoctorId的命名,一个API表明一个资源请求,可以多个斜杠分开小写,如/find/page/{doctorId},尽量不要使用getXXX类似
  4. 对象映射转换MapStruct
  5. 工具类hutool/RestTemplateUtil/HttpUtil/TimeUtil/JsonUtil/RedisUtil/EnumUtil
  6. 自定义异常及全局拦截GlobalException/SystemException
  7. 自定义注解ApiControl/SysOperaLog/HTTP/SDKAOP处理
  8. 对象lombok @Data @Accessors及序列化Serializable
  9. 请求头及系统参数Constants Authorization/responseToken
  10. Jackson序列化及反序列化【LocalDateTime/LocalDate/LocalTime/Long/Date
  11. 日志使用Logger/@Slf4j/@SysOperaLog
  12. 新工程Controller的Handler多入参,禁用JSONObject,统统pojo包下创建Vo对象接收
  13. TK 通用Mapper以及MyBatis-Plus 均为半自动框架,单表CRUD建议使用,前者动态SQL+WeekendSqls实现,多表Mapper XML实现
  14. Controller RequestMapping 定义URL是/模块业务/版本v1/实体业务版本必须,杜绝写大量业务代码;
  15. 枚举提供了解析,针对单参枚举接收、请求对象枚举属性接收,传入code或name即可完成转换,由字典管理的禁用枚举实现;【慎用】
Bean管理及配置
  1. 常用Bean声明RestTemplate/CorsFilter/ObjectMapper

  2. Common公共配置

    common:
      is-need-auth: true
      auth-url: http://192.168.5.11/sso/api/v2/token/validate?jwt=%s&loginType=%s&ApiUrl=%s
      system: Family_Physician
      done-response: true
      db: 
      	type: mysql
      swagger:
      	#【或代码区分公网内网】
      	enabled: true
      	title: API文档名称
      	description: API文档描述
  3. Mybatis-Plus配置【需要引入坐标:mybatis-plus-boot-starter

    #依赖Mybatis-Plus的工程可参考
    #https://192.168.5.50/svn/OMS/trunk/family-physician/family-physician-api
    mybatis-plus:
      #config-location: classpath:mybatis-config.xml
      mapper-locations: classpath:mapper/**/*.xml
      type-aliases-package: com.zhongying.api.homedc.entity
      type-enums-package: com.zhongying.api.enums
      check-config-location: false
      executor-type: simple
      configuration:
        map-underscore-to-camel-case: true
        cache-enabled: false
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      global-config:
        column-underline: true
        db-config:
          id-type: assign_id
          db-type: mysql
  4. TK Mapper 兼容雪花主键,插入即返回主键值

    获取雪花主键:IdWorker.getId(LocalDateTime.now())
    model主键@ID属性配置: @Id + @KeySql(genId = AssignId.class)
    #依赖TK Mapper的非SSO工程可参考
    #https://192.168.5.50/svn/OMS/trunk/online-outpatient/remoteClinic_api
    
    mybatis:
      type-aliases-package: com.zhongying.remoteclinic.api
      mapper-locations: classpath:mybatis/**/*.xml
    
    mapper:
      not-empty: false
      identity: MYSQL
      mappers:
        - com.zhongying.remoteclinic.noscan.BaseMapper
    
    pagehelper:
      helperDialect: mysql
      reasonable: true
      supportMethodsArguments: true
      params: count=countSql
    TK 通用Mapper Page分页示例:
    
    import com.zhongying.common.base.PageTk;
    import com.github.pagehelper.Page;
    import com.github.pagehelper.PageHelper;
    
    public ResponseResult<PageTk<DoctorVo>> findAll(DoctorListReq  doctorlistreq){
            /**
             * 示例一: 拆分调用
             */
            PageHelper.startPage(doctorlistreq.pageIndex, doctorlistreq.pageSize);
            List<DoctorVo> data = doctorMapper.getDoctorsByDepartmentId(doctorlistreq.getHospitalId(), doctorlistreq.getDepartmentId());
            PageTk<DoctorVo> modelPage = PageTk.of(data);
            /**
             * 示例二: 一行调用
             */
            PageTk<Doctor> doctorPage = PageTk.of(PageHelper
                    .startPage(doctorlistreq.pageIndex+1, doctorlistreq.pageSize)
                    .doSelectPage(()->
                    doctorMapper.selectAll()));
            return ResponseResult.success(ResponseCode.SUCCESS, modelPage);
        }
插件说明
  1. authorization

    Token鉴权处理

    //旧工程兼容:false时,Token权限校验由旧工程的UserRoleInterceptor实现
    //新工程必须配置:true, 且Controller的Handler方法均需设置@ApiControl
    common.is-need-auth: false
    
    //需要Token校验的API 请求Header务必传入Authorization: token
    common.auth-url: http://192.168.5.11/sso/api/v2/token/validate?jwt=%s&loginType=%s&ApiUrl=%s
    //前者不放行,校验Token,后者放行不校验Token
    @ApiControl(apiAccessType = BaseEnums.APIAccessType.PRIVATE_API)
    @ApiControl(apiAccessType = BaseEnums.APIAccessType.PUBLIC_API)

    RequestInfo及请求用户信息处理【请求Header务必传入Authorization

    RequestInfo.token()
    RequestInfo.currentUser(DoctorResponseToken.class)
    RequestInfo.currentUser(PatientResponseToken.class)
    RequestInfo.currentUser(BMUserResponseToken.class)
    RequestInfo.currentUser(SsoUserToken.class)
    //除SSO外的旧工程,部分有自定义用户对象类,统一替换为Common中这三类即可
  2. ftp

    ftp:
      server: https://test.zhongyinginfo.com/images/
      ip: test.zhongyinginfo.com
      user: testupload
      password: testupload
      port: 21
    @Autowired
    private FtpUpload ftpUpload;
    
    //返回上传成功的相对路径,配置ftp.server则返回拼接的绝对URL路径
    ftpUpload.fileUpload(file, path, filename)
    
  3. mybatisplus.CodeGenerator

    mp:
      #src包名绝对路径
      src_dir: C:\family-api\src\main\java
      #mapper.xml包名绝对路径
      xml_dir: C:\family-api\src\main\resources\mapper
      author: 作者
      data:
        url: jdbc:mysql://192.168.5.15:3306/family-physician?tinyInt1isBit=false&useUnicode=true&characterEncoding=utf8&useSSL=false
        driver: com.mysql.cj.jdbc.Driver
        name: root
        password: zhongying@2019
        #表名前缀
        table_prex: t_
      #src下包名
      parent: com.zhongying.api
      hasSuper: false
      base_entity_path: com.zhongying.common.base.BaseEntity
      id_name: id
    @Autowired
    private CodeGenerator generator;
    
    @Test
    public void tests() {
    	generator.main();
    }
    
    //Test执行:<Idea Scanner输入需要配置idea.exe.vmoptions或help>
    //【help ->Edit Custom VM Options -> -Deditable.java.test.console=true】
    //执行结果如下:
    请输入模块名:
    remoteclinic
    请输入表名,多个英文逗号分割:
    t_pause_diagnose,t_out_diagnose,doctor_schedule
    
    //最新中代码生成在:
    //C:\family-api\src\main\java\com\zhongying\api\remoteclinic
    //C:\family-api\src\main\resources\mapper
  4. parameter

    参数处理,在被Handler处理前可以进行一些前置处理,比如空字符转Null,加解密等

  5. log/logback-spring.xml

    对所有ControllerHandler进行日志的横切处理

    对所有@SysOperaLog的方法进行操作日志横切处理

    logging:
      level:
        com.baomidou.mybatisplus.samples: debug
    
    log:
      dir: C:\
    //注解在类上,由lombok.extern.slf4j.Slf4j提供,内部封装log,可输出不入库的日志
    @Slf4j
    //注解在Controller的Handler方法上,标识API操作日志,入库
    @SysOperaLog(title = "获取服务套餐列表")
    
    //业务上入库的日志操作类
    @Autowired
    private Logger logger;
    
    log.info("log日志输出:log方式为@Slf4j lombok 提供,默认来输出不需要入库的日志");
    
    Log sysLog = new Log()
        .setType(BaseEnums.LogTypeEnum.Info)
        .setTitle("Bug说明:查询服务套餐报错")
        .setParams(JsonUtil.toJson(vo))
        .setResponse(JsonUtil.toJson(data))
        .setDescription("查询出错的排查");
    //日志入库
    logger.push(sysLog);
  6. rabbitmq

    #基于日志入库采用rabbit消息异步处理,必须配置,否则无法入库
    spring:
      rabbitmq:
        host: 192.168.5.13
        port: 5672
        username: zysso_user
        password: zysso_user
        virtual-host: /datasync
    @Autowired
    private ProducerMQ producerMQ;
    
    //ps.Rabbit ACK 消息发布
    producerMQ.send(BaseEnums.BusinessMQEnum.BusinessLOG, sysLog, BaseMQConfig.TOPIC_EXCHANGE_LOG, BaseMQConfig.TOPIC_ROUTING_KEY)
    
    //新建消费类,监听即可
    //ps.Rabbit ACK 消息消费
    @RabbitListener(containerFactory = "rabbitListenerContainerFactory"
                ,bindings = @QueueBinding(value = @Queue(value = BusinessMQConfig.TOPIC_QUEUE_BUSINESS,durable = "true")
                ,exchange = @Exchange(value = BusinessMQConfig.TOPIC_EXCHANGE_BUSINESS,type = "topic")
                ,key = BusinessMQConfig.TOPIC_ROUTING_KEY))
    public void receive(String message, Message data, Channel channel) throws IOException {
            log.info("消费者收到业务消息:{}", message);
            long deliverTag = data.getMessageProperties().getDeliveryTag();
            //根据消息业务类型:可以根据business 枚举值区分,处理不同业务
            MessageMQ messageMQ = JsonUtil.fromJson(message, MessageMQ.class);
            this.doneBusiness(messageMQ);
            channel.basicAck(deliverTag, false);
    }
  7. emqx

    mqtx:
      broker: tcp://192.168.5.11:1883
      username: mqtt_user
      password: Zy.zhongying@2020
      if-clean-session: true
      timeout: 10
      keep-alive-interval: 20
    //消息订阅:implements ApplicationRunner 容器启动后运行器执行
    //支持同步、异步,可订阅多个topic,一个topic消息只能串行,并发消息需定义多个topic
    @Component
    public class ApplicationInitHandler implements ApplicationRunner {
    
        @Autowired
        private MqtxSubscribe mqtxSubscribe;
        @Autowired
        private MqtxCallBack mqtxCallBack;
    
        @Override
        public void run(ApplicationArguments args) throws Exception {
            mqtxSubscribe.subscribe("/remoteclinicweb/doctor/pay/+",1, mqtxCallBack);
            mqtxSubscribe.asyncSubscribe("/remoteclinicweb/doctor/order/+",1, mqtxCallBack);
        }
    }
    
    //消息消费:继承消息回调类,重写消费方法即可
    @Slf4j
    @Component
    public class MqtxCallBack extends PublishCallback {
    
        @Override
        public void messageArrived(String topic, MqttMessage message) {
            log.info("EMQX收到消息Topic:{}",topic);
            log.info("EMQX收到消息Message:{}", JsonUtil.toJson(message.getPayload()));
        }
    }
    
  8. Redis Lettuce集群 【缓存数据库一致性后续补充】

    redis:
        database: 3
        host: 192.168.5.14
        port: 6379
        password: myredis
        lettuce:
          pool:
            max-active: 20
            max-wait: -1
            max-idle: 8
            min-idle: 0
          shutdown-timeout: 100
        sentinel:
          master: mymaster
          password: myredis
          nodes:
            - 192.168.5.14:26379
            - 192.168.5.13:26380
            - 192.168.5.13:26381
    @Autowired
    private RedisUtil redisUtil;
    
    Sms data = (Sms) redisUtil.get(key)
    redisUtil.set(key, smsObj);
    redisUtil.set(key, smsObj, 5*60*1000);
  9. SMS

    sms:
      accountSid: 8a48b5514fd49643014feda2d623395b
      auth_token: 5b988f83aa5747edb06d021777dacdfc
      server: sandboxapp.cloopen.com
      server_port: 8883
      #互联网医院 应用APPID(必填)
      internetHospitalAppId: 8a216da870e2267e017124b4cda52358
    @Autowired
    private SmsUtil smsUtil;
    
    //发送验证码
    smsUtil.sendVerifyCode("key", mobile);
    //校验验证码
    smsUtil.authVerifyCode("key",mobile, verifyCode)
    

    短信验证码发送:前端统一请求SSO-API即可

    验证码校验:业务API中可直接调用smsUtil.authVerifyCode,发送与校验的传参key,务必一致

  10. dynamic-data-source

spring:
  datasource:
    dynamic:
      #默认数据源
      primary: family
      strict: false
      druid:
        min-idle: 5
        max-active: 100
        filters: stat,wall
        stat:
          merge-sql: true
          log-slow-sql: true
        initial-size: 5
        max-wait: 60000
        time-between-eviction-runs-millis: 60000
        min-evictable-idle-time-millis: 300000
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        maxOpenPreparedStatements: 20
      datasource:
      	#数据源1
        family:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://192.168.5.15:3306/family-physician?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
          username: root
          password: zhongying@2019
          platform: mysql
          type: com.alibaba.druid.pool.DruidDataSource
        #数据源2
        remoteclinic:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://192.168.5.15:3306/remote_clinic?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
          username: root
          password: zhongying@2019
          platform: mysql
          type: com.alibaba.druid.pool.DruidDataSource
@DS("family")
@DS("remoteclinic")
//在对应的Service类、方法加入注解@DS 其内部执行会找到对应的数据源去操作
//针对多数据源嵌套Service跨库调用,如果A方法加了事务
@Transactional(rollbackFor = Exception.class),
//那么在A方法中调用B方法,务必在B方法上加事务传递
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW),
//否则数据源无法完成切换,导致B方法使用了A方法的数据源
SDK
sdk:
	inner:
        sso:
            hospitals-in-ids-url: http://lbs.zyinfo/sso/remoteClinic/doctor/getHospitalsInIds
            hospital_doctors-url: http://lbs.zyinfo/sso/remoteClinic/doctor/getDoctorList?hospitalId={hospitalId}&departCode={departCode}&postionLimit={postionLimit}
        proxy:
            token: fc5c9259bf6d11ea
            sql_query_url: http://lbs.zyinfo/proxy/apiProxy/remoteQuerySql
  1. 跨系统发起的HTTP请求,统统采用@SDK或@HTTP将请求入参出参剥离,便于横切请求数据记录
  2. 通用性的请求,统统封装在SDK工程中,便于其他工程依赖,可直接引入调用
  3. 配置遵循inner/outer 区分内部及外部请求
  4. 复杂入参出参的请求构建request、response对象封装,便于请求属性变动时的灵活调整
  5. 封装好的***Sdk.java文件命名后缀统一,前置名为SDK提供方
  6. ProxySdk BaiduSdk SsoSdk
  7. 雪花主键ID本质上直接调用Mybatis-Plus自带IdWorker.getId(LocalDateTime.now())即可 可包装后交给业务系统调用或者包装在API中交给前端随机获取分布式唯一主键ID
日志排查说明
# 日志从yml配置指定写入文件的路径,记录了每一次外部请求的入参和出参信息,同时也分业务系统存入mysql:system-log
# 排查方式一:服务器上实时请求查看日志打印
  docker ps
  docker logs -f --tail 2000 业务系统容器ID
  可实时联调
# 排查方式二:日志文件下载本地,通过EditPlus等编辑工具,搜索时间点、接口API、提示错误信息等
  mkdir -p /tmp/0427
  docker cp 0ee725da2ba2:/logs/online-pay /tmp/0427
  docker cp 业务系统容器ID:业务系统yml配置写入日志路径 服务器临时存放路径
  Xshell登陆服务器->窗口->传输新建文件->输入上面服务器临时存放路径,将0427文件夹拖到左侧本机路径
  本机路径下编辑器打开catalina.out,依据时间点、API、提示错误进一步锁定异常发生的位置,根据堆栈信息进入抛出异常的类排查
# 排查方式三:利用mysql工具,根据不同系统、开始时间、异常Message、异常堆栈、请求URL、入参params、响应response对异常错误等进入堆栈提供的错误类指定行数前后进行排查
  主要通过mysql 工具自带的过滤搜索指定字段或多个字段来筛选,最后锁定到具体的堆栈信息,复制到编辑器查看异常类所在行数
  最后进入类文件进行排查
# 排查方式四:管理后台,系统日志查看,同排查方式三,区别在于搜索和堆栈信息查看便捷
  同三,区别在于过滤搜索通过页面下拉输入来查询,最后依旧是堆栈里确定发生错误的行数
# 关于系统内发起内部调用HTTP请求,切记只有在调用方法上@SDK才可以在日志上输出入参和出参,作为对业务系统之间内部调用紊乱的情况下的排查切入点。
# 关于Token和请求参数等异常,大部分集中在SSO中比较复杂的鉴权逻辑,Common仅仅只是做了横切所有API入口,向SSO发起鉴权请求;
# 关于异常显示日志切面等报错,大部分集中在数据兼容性,可优化完善日志切面的逻辑代码
# 关于每次请求获取用户信息,源于请求鉴权通过后会将SSO返回的用户属性Map化,缓存在每个request中便于RequestInfo获取,并适配为具体的身份对象

Comments ( 0 )

Sign in for post a comment

About

集成多中间件的脚手架及DEMO,框架能做的统一交由该脚手架,包含鉴权、MP、分布式锁、参数、ZK、校验、日志、Redis、MQ、EMQX、SMS等,业务工程只需依赖配置即可使得脚手架中所有插件可用生效。 spread retract
JavaScript and 5 more languages
Cancel

Releases

No release

Contributors

All

Activities

load more
can not load any more