Rxhttp - lightweight, extensible, easy to use, perfectly compatible with MVVM, MVC architecture network encapsulation class library

rainboy 2021-02-23 14:24:14
rxhttp lightweight extensible easy use


Preface

RxHttp Is based on RxJava2+Retrofit 2.9.0+OkHttp 4.9.0 Lightweight implementation , Perfect compatibility MVVM Architecture of network request encapsulation class library , Cabinet and delicate , Simple and easy to use , Easy to handle network requests .

GitHub

github.com/kongpf8848/…

Bright spot

  • Very little code , Insufficient class library size 100kb, But it's good enough for most of APP Network request task for , Concentrated is the essence _^_

  • Perfect compatibility MVVM,MVC framework , compatible Kotlin and Java,Kotlin+MVVM+RxHttp The combination is more sour and refreshing ,MVVM The official recommendation , Hold tight Google The thighs are right

  • Perfect solution to the thorny problem of generic type erasure , Restore the real type of a generic

  • Born to support web requests and Activity,Fragment Life cycle binding , When the interface is destroyed, the network request callback will be cancelled automatically

  • Natural support BaseUrl, Supports dynamic incoming Url

  • Support customization OkHttpClient.Builder, Highly customizable network request parameters

  • Support Glide Share one with network request OkHttpClient, make the best of OkHttpClient Thread pool and connection pool of , Most of the time, one App One OkHttpClient That's enough

  • Support GET,POST,PUT,DELETE Etc , Support file upload and progress monitoring , Support to upload multiple files at the same time , Support Uri Upload

  • Support file download and progress monitoring , Support large file download , Support breakpoint download

Use requirement

Project based on AndroidX,Java8+,minSdkVersion>=21

Use

implementation 'com.github.kongpf8848:RxHttp:1.0.11'
 Copy code 

To configure ( Optional )

 RxHttpConfig.getInstance()
/**
* Failed retries
*/
.maxRetries(3)
/**
* Time interval between failed retries
*/
.retryDelayMillis(200)
/**
* Customize OkHttpClient.Builder(),RxHttp Support customization OkHttpClient.Builder(),
* If not defined , Then use RxHttp default OkHttpClient.Builder()
*/
.builder(OkHttpClient.Builder().apply {
connectTimeout(60, TimeUnit.SECONDS)
readTimeout(60, TimeUnit.SECONDS)
writeTimeout(60, TimeUnit.SECONDS)
/**
* DEBUG In mode , Add log interceptor , It is recommended to use RxHttp Medium FixHttpLoggingInterceptor, Use OkHttp Of HttpLoggingInterceptor When uploading and downloading, there will be IOException problem
*/
if (BuildConfig.DEBUG) {
addInterceptor(FixHttpLoggingInterceptor().apply {
level = FixHttpLoggingInterceptor.Level.BODY
})
}
})
 Copy code 

Based on using

  • GET/POST/PUT/DELETE/ Upload request
 RxHttp.getInstance()
/**
* get: Request type , for get,post,put,delete,upload, They correspond to each other GET/POST/PUT/DELETE/ Upload request
* context: Context , for Context,Activity or Fragment type , When context by Activity or Fragment Network request and lifecycle binding
*/
.get(context)
/**
* request url, Such as https://www.baidu.com
*/
.url("xxx")
/**
* Request parameter key value pairs , The type is Map<String, Any?>?, Such as hashMapOf("name" to "jack")
*/
.params(map)
/**
* Each network request corresponds to tag value , for null, Used for subsequent manual operation according to tag Cancel the specified network request
*/
.tag("xxx")
/**
* HttpCallback: Network callback , Parameters xxx To return the data model corresponding to the data ,
* similar RxJava Medium Observer,onComplete Only in onNext Execute after callback , If there is an error, it will only call back onError But not onComplete
*/
.enqueue(object : HttpCallback<xxx>() {
/**
* http Callback at the beginning of the request
*/
override fun onStart() {
}
/**
* http Callback on successful request
*/
override fun onNext(response: xxx?) {
}
/**
* http Callback on request failure
*/
override fun onError(e: Throwable?) {
}
/**
* http Call back when the request completes successfully
*/
override fun onComplete() {
}
/**
* Upload progress callback , The request type is upload It will be called back when it's done
*/
override fun onProgress(readBytes: Long, totalBytes: Long) {
}
})
 Copy code 
  • Download request
 RxHttp.getInstance()
/**
* download: Request type , Download request
* context: Context , If you don't need to bind to the lifecycle , It should be delivered applicationContext
*/
.download(context)
/**
* Save the path
*/
.dir(dir)
/**
* Save the file name
*/
.filename(filename)
/**
* Whether it is a breakpoint download , The default is false
*/
.breakpoint(true)
/**
* Download address , Such as http://study.163.com/pub/ucmooc/ucmooc-android-official.apk
*/
.url(url)
/**
* request Tag
*/
.tag(null)
/**
* Download callback
*/
.enqueue(object: DownloadCallback() {
/**
* Callback at the start of download
*/
override fun onStart() {
}
/**
* Call back when the download is complete
*/
override fun onNext(response: DownloadInfo?) {
}
/**
* Call back when download fails
*/
override fun onError(e: Throwable?) {
}
/**
* When the download is complete, call back
*/
override fun onComplete() {
}
/**
* Download progress callback
*/
override fun onProgress(readBytes: Long, totalBytes: Long) {
}
})
 Copy code 
  • Cancel the request
 /**
* tag:Any?, request Tag, Corresponding to... In the network request Tag value
* If not null, Cancel the specified network request ,
* If null, Cancel all network requests
*/
RxHttp.getInstance().cancelRequest(tag)
 Copy code 

Project practice

Here we assume that the data format returned by the server is {"code":xxx,"data":T,"msg":""}, among code Is the response code , integer , be equal to 200 When it comes to success , The rest are failures ,data The corresponding data type is generic (boolean,int,double,String, object { }, Array [ ] Other types )

{
"code": 200,
"data":T,
"msg": ""
}
 Copy code 

Corresponding Response Class is

class TKResponse<T>(val code:Int,val msg: String?, val data: T?) : Serializable {
companion object{
const val STATUS_OK=200
}
fun isSuccess():Boolean{
return code== STATUS_OK
}
}
 Copy code 
  • MVC project

    • Definition MVCHttpCallback, It is used to call back the result of network request to UI Interface

    abstract class MVCHttpCallback<T> {
    private val type: Type
    init {
    val arg = TypeUtil.getType(javaClass)
    type = TypeBuilder
    .newInstance(TKResponse::class.java)
    .addTypeParam(arg)
    .build()
    }
    fun getType(): Type {
    return this.type
    }
    /**
    * Callback at the beginning of the request , You can load here loading Dialog, etc , Null implementation by default
    */
    open fun onStart() {}
    /**
    * Abstract method , Request successful callback , The returned content is generic , Corresponding TKResponse Of data
    */
    abstract fun onSuccess(result: T?)
    /**
    * Abstract method , Request failed callback , The returned content is code( Error code ),msg( error message )
    */
    abstract fun onFailure(code: Int, msg: String?)
    /**
    * Upload progress callback , Null implementation by default
    */
    open fun onProgress(readBytes: Long, totalBytes: Long) {}
    /**
    * Callback on request completion , This method will not be called back until the request is successful , Null implementation by default
    */
    open fun onComplete() {}
    }
     Copy code 
    • Define the network interface , encapsulation GET/POST Wait for network request

    object MVCApi {
    /**
    * GET request
    * context: Context
    * url: request url
    * params: parameter list , for null
    * tag: Identify a network request
    * callback: Network request callback
    */
    inline fun <reified T> httpGet(
    context: Context,
    url: String,
    params: Map<String, Any?>?,
    tag: Any? = null,
    callback: MVCHttpCallback<T>
    ) {
    RxHttp.getInstance().get(context)
    .url(url)
    .params(params)
    .tag(tag)
    .enqueue(simpleHttpCallback(callback))
    }
    /**
    * POST request
    * context: Context
    * url: request url
    * params: parameter list , for null
    * tag: Identify a network request
    * callback: Network request callback
    */
    inline fun <reified T> httpPost(
    context: Context,
    url: String,
    params: Map<String, Any?>?,
    tag: Any? = null,
    callback: MVCHttpCallback<T>
    ) {
    RxHttp.getInstance().post(context)
    .url(url)
    .params(params)
    .tag(tag)
    .enqueue(simpleHttpCallback(callback))
    }
    ......
    inline fun <reified T> simpleHttpCallback(callback: MVCHttpCallback<T>): HttpCallback<TKResponse<T>> {
    return object : HttpCallback<TKResponse<T>>(callback.getType()) {
    override fun onStart() {
    super.onStart()
    callback.onStart()
    }
    override fun onNext(response: TKResponse<T>?) {
    if (response != null) {
    if (response.isSuccess()) {
    callback.onSuccess(response.data)
    } else {
    return onError(ServerException(response.code, response.msg))
    }
    } else {
    return onError(NullResponseException(TKErrorCode.ERRCODE_RESPONSE_NULL, TKErrorCode.ERRCODE_RESPONSE_NULL_DESC))
    }
    }
    override fun onError(e: Throwable?) {
    handleThrowable(e).run {
    callback.onFailure(first, second)
    }
    }
    override fun onComplete() {
    super.onComplete()
    callback.onComplete()
    }
    override fun onProgress(readBytes: Long, totalBytes: Long) {
    super.onProgress(readBytes, totalBytes)
    callback.onProgress(readBytes, totalBytes)
    }
    }
    }
     Copy code 
    • stay View Layering as Activity Call the network interface

    MVCApi.httpGet(
    context = baseActivity,
    url = TKURL.URL_GET,
    params = null,
    tag = null,
    callback = object : MVCHttpCallback<List<Banner>>() {
    override fun onStart() {
    LogUtils.d(TAG, "onButtonGet onStart() called")
    }
    override fun onSuccess(result: List<Banner>?) {
    Log.d(TAG, "onButtonGet onSuccess() called with: result = $result")
    }
    override fun onFailure(code: Int, msg: String?) {
    Log.d(TAG, "onButtonGet onFailure() called with: code = $code, msg = $msg")
    }
    override fun onComplete() {
    Log.d(TAG, "onButtonGet onComplete() called")
    }
    })
     Copy code 

    For specific use, please refer to demo Code ,demo There are detailed examples in MVC How to use the project RxHttp

  • MVVM project

    • Definition Activity Base class BaseMvvmActivity
    abstract class BaseMvvmActivity<VM : BaseViewModel, VDB : ViewDataBinding> : AppCompatActivity(){
    lateinit var viewModel: VM
    lateinit var binding: VDB
    protected abstract fun getLayoutId(): Int
    final override fun onCreate(savedInstanceState: Bundle?) {
    onCreateStart(savedInstanceState)
    super.onCreate(savedInstanceState)
    binding = DataBindingUtil.setContentView(this, getLayoutId())
    binding.lifecycleOwner = this
    createViewModel()
    onCreateEnd(savedInstanceState)
    }
    protected open fun onCreateStart(savedInstanceState: Bundle?) {}
    protected open fun onCreateEnd(savedInstanceState: Bundle?) {}
    /**
    * establish ViewModel
    */
    private fun createViewModel() {
    val type = findType(javaClass.genericSuperclass)
    val modelClass = if (type is ParameterizedType) {
    type.actualTypeArguments[0] as Class<VM>
    } else {
    BaseViewModel::class.java as Class<VM>
    }
    viewModel = ViewModelProvider(this).get(modelClass)
    }
    private fun findType(type: Type): Type?{
    return when(type){
    is ParameterizedType -> type
    is Class<*> ->{
    findType(type.genericSuperclass)
    }
    else ->{
    null
    }
    }
    }
    }
     Copy code 
    • Definition ViewModel Base class of BaseViewModel
    open class BaseViewModel(application: Application) : AndroidViewModel(application) {
    /**
    * Network warehouse
    */
    protected val networkbaseRepository: NetworkRepository = NetworkRepository.instance
    /**
    * Context
    */
    protected var context: Context = application.applicationContext
    }
     Copy code 
    • Define the network warehouse , Encapsulating network interfaces
    /**
    * MVVM Architecture network warehouse
    * UI->ViewModel->Repository->LiveData(ViewModel)->UI
    */
    class NetworkRepository private constructor() {
    companion object {
    val instance = NetworkRepository.holder
    }
    private object NetworkRepository {
    val holder = NetworkRepository()
    }
    inline fun <reified T> wrapHttpCallback(): MvvmHttpCallback<T> {
    return object : MvvmHttpCallback<T>() {
    }
    }
    inline fun <reified T> newCallback(liveData: MutableLiveData<TKState<T>>): HttpCallback<TKResponse<T>> {
    val type = wrapHttpCallback<T>().getType()
    return object : HttpCallback<TKResponse<T>>(type) {
    override fun onStart() {
    liveData.value = TKState.start()
    }
    override fun onNext(response: TKResponse<T>?) {
    liveData.value = TKState.response(response)
    }
    override fun onError(e: Throwable?) {
    liveData.value = TKState.error(e)
    }
    override fun onComplete() {
    /**
    * Pro - , Don't do anything here , Don't give LiveData assignment , prevent onNext Corresponding LiveData Data is covered ,
    * stay TKState class handle Method will handle callbacks in particular , Don't worry
    */
    }
    override fun onProgress(readBytes: Long, totalBytes: Long) {
    liveData.value = TKState.progress(readBytes, totalBytes)
    }
    }
    }
    inline fun <reified T> httpGet(
    context: Context,
    url: String,
    params: Map<String, Any?>?,
    tag: Any? = null
    ): MutableLiveData<TKState<T>> {
    val liveData = MutableLiveData<TKState<T>>()
    RxHttp.getInstance()
    .get(context)
    .url(url)
    .params(params)
    .tag(tag)
    .enqueue(newCallback(liveData))
    return liveData
    }
    inline fun <reified T> httpPost(
    context: Context,
    url: String,
    params: Map<String, Any?>?,
    tag: Any? = null
    ): MutableLiveData<TKState<T>> {
    val liveData = MutableLiveData<TKState<T>>()
    RxHttp.getInstance().post(context)
    .url(url)
    .params(params)
    .tag(tag)
    .enqueue(newCallback(liveData))
    return liveData
    }
    inline fun <reified T> httpPostForm(
    context: Context,
    url: String,
    params: Map<String, Any?>?,
    tag: Any? = null
    ): MutableLiveData<TKState<T>> {
    val liveData = MutableLiveData<TKState<T>>()
    RxHttp.getInstance().postForm(context)
    .url(url)
    .params(params)
    .tag(tag)
    .enqueue(newCallback(liveData))
    return liveData
    }
    inline fun <reified T> httpPut(
    context: Context,
    url: String,
    params: Map<String, Any?>?,
    tag: Any? = null
    ): MutableLiveData<TKState<T>> {
    val liveData = MutableLiveData<TKState<T>>()
    RxHttp.getInstance().put(context)
    .url(url)
    .params(params)
    .tag(tag)
    .enqueue(newCallback(liveData))
    return liveData
    }
    inline fun <reified T> httpDelete(
    context: Context,
    url: String,
    params: Map<String, Any?>?,
    tag: Any? = null
    ): MutableLiveData<TKState<T>> {
    val liveData = MutableLiveData<TKState<T>>()
    RxHttp.getInstance().delete(context)
    .params(params)
    .url(url)
    .tag(tag)
    .enqueue(newCallback(liveData))
    return liveData
    }
    /**
    * Upload
    * Support uploading multiple files ,map Corresponding value The type is File Type or Uri type
    * Monitor the upload progress
    val map =Map<String,Any>()
    map.put("model", "xiaomi")
    map.put("os", "android")
    map.put("avatar",File("xxx"))
    map.put("video",uri)
    */
    inline fun <reified T> httpUpload(
    context: Context,
    url: String,
    params: Map<String, Any?>?,
    tag: Any? = null
    ): MutableLiveData<TKState<T>> {
    val liveData = MutableLiveData<TKState<T>>()
    RxHttp.getInstance().upload(context)
    .url(url)
    .params(params)
    .tag(tag)
    .enqueue(newCallback(liveData))
    return liveData
    }
    /**
    * download
    * context: Context , If you don't need to bind to the lifecycle , It should be delivered applicationContext
    * url: Download address
    * dir: Local directory path
    * filename: Save the file name
    * callback: Download progress callback
    * md5: Download the file MD5 value
    * breakpoint: Whether breakpoint download is supported , The default is true
    */
    fun httpDownload(context: Context, url: String, dir: String, filename: String, callback: DownloadCallback, md5: String? = null, breakPoint: Boolean = true, tag: Any? = null) {
    RxHttp.getInstance().download(context).dir(dir).filename(filename).breakpoint(breakPoint).md5(md5).url(url).tag(tag).enqueue(callback)
    }
    }
     Copy code 
    • Definition TKState class , Used to convert network callbacks to LiveData
    /**
    * take HttpCallback The callback is converted to the corresponding LiveData
    */
    class TKState<T> {
    var state: Int = 0
    var code = TKErrorCode.ERRCODE_UNKNOWN
    var msg: String? = null
    var data: T? = null
    var progress: Long = 0
    var total: Long = 0
    @JvmOverloads
    constructor(state: Int, data: T? = null, msg: String? = "") {
    this.state = state
    this.data = data
    this.msg = msg
    }
    constructor(state: Int, throwable: Throwable?) {
    this.state = state
    handleThrowable(throwable).run {
    this@TKState.code = first
    this@TKState.msg = second
    }
    }
    constructor(state: Int, progress: Long, total: Long) {
    this.state = state
    this.progress = progress
    this.total = total
    }
    fun handle(handleCallback: HandleCallback<T>.() -> Unit) {
    val callback = HandleCallback<T>()
    callback.apply(handleCallback)
    when (state) {
    START -> {
    callback.onStart?.invoke()
    }
    SUCCESS -> {
    callback.onSuccess?.invoke(data)
    }
    FAIL -> {
    callback.onFailure?.invoke(code, msg)
    }
    PROGRESS -> {
    callback.onProgress?.invoke(progress, total)
    }
    }
    if (state == SUCCESS || state == FAIL) {
    callback.onComplete?.invoke()
    }
    }
    open class HandleCallback<T> {
    var onStart: (() -> Unit)? = null
    var onSuccess: ((T?) -> Unit)? = null
    var onFailure: ((Int, String?) -> Unit)? = null
    var onComplete: (() -> Unit)? = null
    var onProgress: ((Long, Long) -> Unit)? = null
    fun onStart(callback: (() -> Unit)?) {
    this.onStart = callback
    }
    fun onSuccess(callback: ((T?) -> Unit)?) {
    this.onSuccess = callback
    }
    fun onFailure(callback: ((Int, String?) -> Unit)?) {
    this.onFailure = callback
    }
    fun onComplete(callback: (() -> Unit)?) {
    this.onComplete = callback
    }
    fun onProgress(callback: ((Long, Long) -> Unit)?) {
    this.onProgress = callback
    }
    }
    companion object {
    const val START = 0
    const val SUCCESS = 1
    const val FAIL = 2
    const val PROGRESS = 3
    fun <T> start(): TKState<T> {
    return TKState(START)
    }
    fun <T> response(response: TKResponse<T>?): TKState<T> {
    if (response != null) {
    if (response.isSuccess()) {
    return TKState(SUCCESS, response.data, null)
    } else {
    return error(ServerException(response.code, response.msg))
    }
    } else {
    return error(NullResponseException(TKErrorCode.ERRCODE_RESPONSE_NULL, TKErrorCode.ERRCODE_RESPONSE_NULL_DESC))
    }
    }
    fun <T> error(t: Throwable?): TKState<T> {
    return TKState(FAIL, t)
    }
    fun <T> progress(progress: Long, total: Long): TKState<T> {
    return TKState(PROGRESS, progress, total)
    }
    }
    }
     Copy code 
    • After a series of packages , Last in View Layering as Activity in ViewModel call Repository The interface
     viewModel.testPost(hashMapOf(
    "name" to "jack",
    "location" to "shanghai",
    "age" to 28)
    )
    .observeState(this) {
    onStart {
    LogUtils.d(TAG, "onButtonPost() onStart called")
    }
    onSuccess {
    LogUtils.d(TAG, "onButtonPost() onSuccess called:${it}")
    }
    onFailure { code, msg ->
    ToastHelper.toast("onButtonPost() onFailure,code:${code},msg:${msg}")
    }
    onComplete {
    LogUtils.d(TAG, "onButtonPost() onComplete called")
    }
    }
     Copy code 

    Specific use should also refer to demo Code ,demo There are detailed examples in MVVM How to use the project RxHttp

Download is highly recommended Demo Code ,Demo There are detailed examples in , demonstration MVVM And MVC How architecture is used RxHttp, If this article helps you , You can think about giving me some praise _^_

Demo

github.com/kongpf8848/…

版权声明
本文为[rainboy]所创,转载请带上原文链接,感谢
https://javamana.com/2021/02/20210223142045839y.html

  1. Lazy load lazy load of SAP ui5 JavaScript files
  2. Add filter and execute filter in excel by Java
  3. Liteos: inventory those important data structures
  4. HDFS依然是存储的王者
  5. [MySQL]事务的MVCC原理与幻读
  6. 93.7%的程序员!竟然都不知道Redis为什么默认16个数据库?
  7. Java 集合处理/ 空值处理/ 异常处理,使用心得分享!
  8. Spring Authorization Server 全新授权服务器整合使用
  9. Spring Security 实战干货:OAuth2登录获取Token的核心逻辑
  10. Java中各种锁的原理解析
  11. java的byte和C#的byte的不同之处
  12. Java 在Excel中添加筛选器并执行筛选
  13. HDFS is still the king of storage
  14. Mvcc principle and unreal reading of [MySQL] transaction
  15. 93.7% of programmers! Why does redis default to 16 databases?
  16. Java collection processing / null value processing / exception processing, use experience sharing!
  17. Integrated use of new authorization server of spring authorization server
  18. Spring security real combat dry goods: the core logic of oauth2 login to obtain token
  19. Principle analysis of various locks in Java
  20. Differences between Java byte and C byte
  21. Add filter and execute filter in excel by Java
  22. Dialogue in spring
  23. 解决Docker MySQL无法被宿主机访问的问题
  24. Oracle OCP 19c 认证1Z0-083考试题库(第1题)
  25. Solve the problem that docker MySQL cannot be accessed by the host
  26. Oracle OCP 19C certification 1z0-083 examination question bank (question 1)
  27. 在 2021 年你需要掌握的 7 种关于 JavaScript 的数组方法
  28. Seven array methods for JavaScript you need to master in 2021
  29. 在 2021 年你需要掌握的 7 种关于 JavaScript 的数组方法
  30. Struts2 + Json _ 配置,异常解决及深入了解Struts2返回JSON数据的原理及具体应用范例
  31. Seven array methods for JavaScript you need to master in 2021
  32. Struts2 + Json _ Configuration, exception resolution and in-depth understanding of Struts2 return JSON data principle and specific application examples
  33. (三)MySQL锁机制 + 事务
  34. (3) MySQL lock mechanism + transaction
  35. 在 2021 年你需要掌握的 7 种关于 JavaScript 的数组方法
  36. Seven array methods for JavaScript you need to master in 2021
  37. 基于Kafka和Elasticsearch构建实时站内搜索功能的实践
  38. Practice of building real time search function in the website based on Kafka and elasticsearch
  39. Golang 实现 Redis(9): 使用GeoHash 搜索附近的人
  40. RxHttp - 轻量级、可扩展、易使用、完美兼容MVVM、MVC架构的网络封装类库
  41. Golang 实现 Redis(9): 使用GeoHash 搜索附近的人
  42. RxHttp - 轻量级、可扩展、易使用、完美兼容MVVM、MVC架构的网络封装类库
  43. Golang realizes redis (9): using geohash to search nearby people
  44. Rxhttp - lightweight, extensible, easy to use, perfectly compatible with MVVM, MVC architecture network encapsulation class library
  45. Golang realizes redis (9): using geohash to search nearby people
  46. Rxhttp - lightweight, extensible, easy to use, perfectly compatible with MVVM, MVC architecture network encapsulation class library
  47. 读懂框架设计的灵魂 — Java 反射机制
  48. 治疗磁盘空间不足焦虑症,释放容器占用空间——Win10+docker篇
  49. 别再用jodatime了!全网最权威Java8日期时间类LocalDate、LocalDateTime详解
  50. Understanding the soul of framework design java reflection mechanism
  51. 配置客户端以安全连接到Apache Kafka集群4:TLS客户端身份验证
  52. Treating anxiety of insufficient disk space and releasing space occupied by containers -- win10 + docker
  53. Don't use jodatime any more! The most authoritative java 8 date and time classes in the whole network: detailed explanation of localdate and localdatetime
  54. Configure clients to connect securely to Apache Kafka Cluster 4: TLS client authentication
  55. Spring break
  56. 高性能MySQL(三):Schema与数据类型优化
  57. High performance mysql (3): schema and data type optimization
  58. redis解决缓存、击穿、雪崩
  59. redis
  60. 骑士卡:基于Kafka搭建消息中心,上亿消息推送轻松完成