hc-basis-node

0.0.9 • Public • Published

和社区技术后端基础包

安装

$ npm install hc-basis-node

简要介绍

使用方法

此基础包根据功能分类划分的文件, 分为以下文件

  每一个文件都是一个特殊的单利类, 即一个类只有一个实例对象, 方法是类中的属性, 所有类都在index.js文件中实例化出了一个对象, 所有对象合并成为一个全局对象, 命名为 $ , 调用的时候统一使用 "$.对象名.方法名"调用即可.

  一些非常常用的方法, 对调用进行了优化, 直接放到了最外层调用, 简化的函数如下:

$.upload()
$.time10()
$.time13()
$.md5()
$.sha1()
$.flush()

使用

第1步:配置全局配置文件 conf.js

conf.js 示例文件

// ---------------------------------------------------------------------------- Package
const _ = require('lodash')
// ---------------------------------------------------------------------------- Port
const port = 3000
// ---------------------------------------------------------------------------- Common
const common = {
  encrypt: {
    deskey: 'KWes6(ken9!d5478w2(*#12!'    // 加密使用的24位key
  },
  exception: {
    mail: [],                             // 报错时给管理员发送email的地址
    mobile: []                            // 报错时给管理员发送短信的手机号
  },
  router: {
    dir: process.cwd() + '/router',       // 导入路由的路径
    ext: ['.js'],                         // 导入路由文件的类型设置
    exclude: []                           // 导入路由时忽略的文件
  },
  upload: {
    rule: {                               // 上传文件时的接收规则配置项
      product:  {project: 'shop',  category: 'product',   max: 1024000, type: 'img'}
    }
  },
  aliyun: {                               // 阿里云服务器的配置项
    accessKey: 'LTAISqgBxKWM0BqQ',
    accessSecret: 'ZNb0pBReD1Je5S8HwuhXe9y7KGogz4',
    oss: {
      region: 'oss-cn-shanghai',
      bucket: 'hoss-upload'
    }
  }
}
// ---------------------------------------------------------------------------- Special
const conf = {
  development: {                          // 开发环境下的配置项
    accept:         "http://localhost",
    uppath:         "c:\\uptemp",
    session: {                            // session配置项
      redis: {
        host:       "localhost",
        port:       6379
      },
      domain:       '192.168.0.5',
      path:         '/',
    },
    mysql: {                              // mysql数据库配置项
      main: {
        host:       "127.0.0.1",
        user:       "root",
        password:   "123456",
        database:   "test"
      },
      user: {
        host:       "127.0.0.1",
        user:       "root",
        password:   "123456",
        database:   "user"
      }
    },
    redis: {                              // redis配置项
      host:       "localhost",
      port:       6379
    },
    wechat: {                             // 微信配置项
      appid:        "wx9852fd56ec8cbbc5",
      appsecret:    "021f04101dee3f77e856ab7563a9848e",
      scope:        "snsapi_userinfo",
      jsapi_domain: "http://shop.plat.hesq.com.cn/",
      mch_id:       "1232291902",
      key:          "f44773bf001fe9328beab30f1b9640ad",
      notify_url:   "http://hrest.ngrok.cc/wepay/notify",
      trade_type:   "JSAPI"
    }
  },
  // -------------------------------------------------------------------------- Pro
  production: {                           // 生产环境下的配置项
    accept:         "http://localhost",
    uptemp:         "uptemp",
    session: {                            // session配置项
      redis: {
        host:       "localhost",
        port:       6379
      },
      domain:       '192.168.0.5',
      path:         '/',
    },
    mysql: {                              // mysql数据库配置项
      main: {
        host:       "127.0.0.1",
        user:       "root",
        password:   "123456",
        database:   "test"
      },
      user: {
        host:       "127.0.0.1",
        user:       "root",
        password:   "123456",
        database:   "user"
      }
    },
    redis: {                              // redis配置项
      host:       "localhost",
      port:       6379
    },
    wechat: {                             // 微信配置项
      appid:        "wx9852fd56ec8cbbc5",
      appsecret:    "021f04101dee3f77e856ab7563a9848e",
      scope:        "snsapi_userinfo",
      jsapi_domain: "http://shop.plat.hesq.com.cn/",
      mch_id:       "1232291902",
      key:          "f44773bf001fe9328beab30f1b9640ad",
      notify_url:   "http://hrest.ngrok.cc/wepay/notify",
      trade_type:   "JSAPI"
    }
  }
}
// ---------------------------------------------------------------------------- Env
let env = process.env.NODE_ENV === 'development' ? 'development' : 'production'
// ---------------------------------------------------------------------------- Exports
module.exports = _.merge({port}, common, conf[env])       // 将所有配置项合并导出

第2步:引用hc-basis-node包并传入全局配置文件

index.js 示例文件

// ---------------------------------------------------------------------------- Package
const path = require('path')
const Koa = require('koa')
const router = require('koa-router')()
const common = require('hw-common-node')
const serve = require('koa-static')
const render = require('koa-ejs')
// ---------------------------------------------------------------------------- Global
require('hc-basis-node')(require('./conf'))
// ---------------------------------------------------------------------------- App与第三方中间件
global.app = new Koa()
// ---------------------------------------------------------------------------- Common
app.use(common())
// ---------------------------------------------------------------------------- Static Serve
app.use(serve('.'))
// ---------------------------------------------------------------------------- Render
render(app, {
  root: path.join(__dirname, 'router'),
  layout: 'layout',
  viewExt: 'html',
  cache: false,
  debug: false
})
// ---------------------------------------------------------------------------- Router
$.router.getRoutes(router)
app.use(router.routes())
app.use(router.allowedMethods())
// ---------------------------------------------------------------------------- Listen
app.listen($.conf.port)
console.log('开始监听:' + $.conf.port)

工具包函数


Encrypt


$.md5(strSrc)


md5加密

参数

  1. strSrc(String):源字符串

返回值

(String):md5加密后的字符串

示例

/**
 * 返回加密后的字符串
 */
console.log($.md5('abcdefg'))
//7ac66c0f148de9519b8bd264312c4d64

$.sha1(strSrc)


sha1加密

参数

  1. strSrc(String):源字符串

返回值

(String):sha1加密后的字符串

示例

/**
 * 返回加密后的字符串
 */
console.log($.sha1('abcdefg'))
//2fb5e13419fc89246865e7a324f476ec624e8740

$.encrypt.desEncrypt(strSrc)


字符串加密

参数

  1. strSrc(String):源字符串

返回值

(String):加密后的字符串

示例

/**
 * 返回加密后的字符串
 */
$.encrypt.desEncrypt('abcdefg')

$.encrypt.desDecrypt(strSrc)


字符串加密

参数

  1. strSrc(String):源字符串

返回值

(String):解密后的字符串

示例

/**
 * 返回解密后的字符串
 */
$.encrypt.desDecrypt('abcdefg')

返回顶部


ERR


$.err


各种类型的错误对象

示例

$.err.ValidConfigError    // Valid配置项错误
$.err.HttpParamNotNull    // Http参数不能为空
$.err.HttpParamIllegal    // Http参数验证错误
$.err.FuncParamNotNull    // Func参数不能为空
$.err.FuncParamIllegal    // Func参数验证错误
$.err.IOError             // IO出错,如写入文件、读取文件
$.err.MysqlError          // Mysql操作出错
$.err.RedisError          // Redis操作出错
$.err.HttpError           // Http向外访问出错
$.err.ThirdError          // 第三方返回出错
$.err.PowerError          // 用户权限错误
$.err.PowerTimeout        // 用户权限超时

返回顶部


EXCEL


$.excel.read(strFile)


【Promise】读取一个xlsx文件为json对象

参数

  1. strFile(String):需要读取的文件路径

返回值

(Object):成功返回取回的Json格式对象

示例

/**
 * 返回Json格式对象
 * {
 *  sheet1: [
 *    [1, 2, 3, 4, 5],
 *    [1, 2, 3, 4, 5],
 *    [1, 2, 3, 4, 5]
 *  ],
 *  sheet2: [
 *    [1, 2, 3, 4, 5],
 *    [1, 2, 3, 4, 5],
 *    [1, 2, 3, 4, 5]
 *  ]
 * }
 */
let xlsx = await $.excel.read('./12.xlsx')

$.excel.write(strFile, objContent)


【Promise】将指定数据对象写入excel文件(仅xlsx扩展名)

参数

  1. strFile(String):需要写入的文件名
  2. objContent(Object):需要写入的数据对象

返回值

(string | object):成功返回 'ok', 失败返回错误对象

示例

let strFile = './12.xlsx'
let objContent = {
  sheet1: [
    [1, 2, 3, 4, 5],
    [1, 2, 3, 4, 5],
    [1, 2, 3, 4, 5]
  ],
  sheet2: [
    [1, 2, 3, 4, 5],
    [1, 2, 3, 4, 5],
    [1, 2, 3, 4, 5]
  ]
}
await $.excel.write(strFile, objContent)

返回顶部


EXCEPTION


$.Exception.handleException(ctx, err)


处理错误

参数

  1. ctx(Object):Koa上下文对象
  2. err(Object):需要抛出的错误对象

示例

$.Exception.handleException(ctx, err)

返回顶部


FLUSH


$.flush(ctx, anyContent)


记录日志,并将内容输出到客户端

参数

  1. ctx(Object):Koa2 上下文对象
  2. anyContent(Any):需要输出到客户端的内容

示例

$.flush(ctx, anyContent)

返回顶部


GEN


$.gen.char([intLength])


生成随机Char类型的字符串

参数

  1. [intLength](Number):生成随机字符串的长度, 默认为4

返回值

(String):已生成的随机字符串

示例

console.log($.gen.char(4))
console.log($.gen.char(8))
// jkzd
// kajldxod

$.gen.number([intLength])


生成随机Number类型的字符串

参数

  1. [intLength](Number):生成随机字符串的长度, 默认为4

返回值

(String):已生成的随机字符串

示例

console.log($.gen.number(4))
console.log($.gen.number(8))
// 4536
// 89562468

$.gen.mixed([intLength])


生成随机Mixed类型的字符串

参数

  1. [intLength](Number):生成随机字符串的长度, 默认为4

返回值

(String):已生成的随机字符串

示例

console.log($.gen.mixed(4))
console.log($.gen.mixed(8))
// f45d
// fd15ds1f

返回顶部


HTTP


$.http.get(strUrl)


【Promise】http的get请求

参数

  1. strUrl(String):请求地址

返回值

(Any):get请求的返回值

示例

/**
 * 返回get请求的返回结果
 */
let resp = await $.http.get('http://www.baidu.com')

$.http.post(strUrl, [objParams])


【Promise】http的post请求

参数

  1. strUrl(String):请求地址
  2. [objParams](Object):请求参数

返回值

(Any):post请求的返回值

示例

/**
 * 返回get请求的返回结果
 */
let resp = await $.http.post('http://www.baidu.com', {name: 'xxx', age: 18})

返回顶部


IO


$.io.writeFile(strPath, buffer)


【Promise】保存文件

参数

  1. strPath(String):保存文件的全路径
  2. buffer(Buffer):需要保存到文件的buffer

返回值

(Bool):成功时返回true

示例

await $.io.writeFile('./a/1.txt', buffer)

$.io.moveFile(strPathSrc, strPathTarget)


【Promise】移动文件

参数

  1. strPathSrc(String):源路径
  2. strPathTarget(String):目标路径

返回值

(Bool):成功时返回true

示例

await $.io.moveFile('./a/1.txt', './b/1.txt')

$.io.createDir(strDir)


【Normal】循环遍历创建目录,同步版本使用

参数

  1. strDir(String):需要创建的目录路径

示例

await $.io.createDir(strDir)

$.io.getFiles(strDir, arrExt, arrExclude, result)


遍历目录获取所有文件数组

参数

  1. strDir(String):需要遍历的目录名称
  2. arrExt(Array):允许加入到结果的文件扩展名数组
  3. arrExclude(Array):需要排除的文件名数组
  4. result(Array):该目录下符合条件的所有文件

返回值

(Array):将组织好的新的 result 返回

示例

$.io.getFiles(strDir, arrExt, arrExclude, result)

返回顶部


LOG


$.log


记录不同类型的log日志

示例

$.log.info(strContent)      // 记录消息日志
$.log.warn(strContent)      // 记录警告日志
$.log.error(strContent)     // 记录错误日志
$.log.third(strContent)     // 记录第三方错误日志
$.log.get(strContent)       // 记录get类型日志
$.log.post(strContent)      // 记录post类型日志
$.log.put(strContent)       // 记录put类型日志
$.log.delete(strContent)    // 记录delete类型日志

返回顶部


MYSQL


$.mysql.query(objConfig, anySql, arrParam)


查询一条或多条sql语句(查)

参数

  1. objConfig(Object):数据库连接配置对象
  2. anySql(Any):sql语句字符串,可使用?参数,必须是字符串或数组
  3. arrParam(Array):sql语句对应参数,当sql语句为字符串时该参数为一维数组,sql语句为数组时该参数为二维数组,sql语句无参数时该值为nul

返回值

(array):单条查询时返回一维数组, 多条查询时返回二维数组

示例一: 查询单条语句

/**
 * 返回一维数组. 返回值示例
 * [ {id: 25, name: '火龙果'} ]
 */
$.mysql.query($.conf.mysql.main, 'select id, name from product where id=?', [25])

示例二: 查询多条语句

/**
 * 返回二维数组. 返回值示例
 * [
 *     [
 *          {id:1, openid: 'xxxxx', nick: 'xxxx'},
 *          {id:2, openid: 'xxxxx', nick: 'xxxx'},
 *          {id:3, openid: 'xxxxx', nick: 'xxxx'},
 *          {id:4, openid: 'xxxxx', nick: 'xxxx'},
 *     ]
 *     [ {id: 25, name: '火龙果'} ]
 * ]
 */
// 声明sqls, params
let sqls = []
let params = []
 
// 第一条查询语句
sqls.push('select * from user')
params.push(null)
 
// 第二条查询语句
sqls.push('select id, name from product where id=?')
params.push([25])
 
// 查询
$.mysql.query($.conf.mysql.main, sqls, params)

$.mysql.push(objConfig, anySql, arrParam)


修改一条或多条语句 (增删改)

参数

  1. objConfig(Object):数据库连接配置对象
  2. anySql(Any):sql语句字符串,可使用?参数,必须是字符串或数组
  3. arrParam(Array):sql语句对应参数,当sql语句为字符串时该参数为一维数组,sql语句为数组时该参数为二维数组,sql语句无参数时该值为nul

返回值

(array):单条sql语句时返回对象, 多条sql语句时返回数组对象

示例一: 单条语句

/**
 * 返回执行结果. 返回值示例
 * 插入语句返回:{insertId: 25}   更新语句返回:{changedRows: 25}  删除语句返回:{affectedRows: 25}
 */
$.mysql.push($.conf.mysql.main, 'insert into user (name, age) values (?,?)', ['XXX', 25])

示例二: 多条语句

/**
 * 返回二维数组. 返回值示例
 * [
 *  {insertId: 25},     // 插入的时候的返回插入的数据id
 *  {changedRows: 10},  // 更新的时候的返回更新的数据条数
 *  {affectedRows: 10}  // 删除的时候的返回删除的数据条数
 * ]
 */
// 声明sqls, params
let sqls = []
let params = []
 
// 第一条语句
sqls.push('insert into user (name, age) values (?,?)')
params.push(['XXX', 25])
 
// 第二条语句
sqls.push('update user set age=? where id=?')
params.push([18, 25])
 
// 第三条语句
sqls.push('delete from user where age>?')
params.push([20])
 
// 执行
$.mysql.push($.conf.mysql.main, sqls, params)

返回顶部


QRCODE


$.qrcode.create(ctx)


【Promise】生成二维码

参数

  1. ctx(Object):Koa2 上下文对象

返回值

(Object):返回生成二维码路径{path: '从根目录开始的路径', url: '从project开始的路径, 阿里云访问'}

示例

 

返回顶部


REDIS


$.redis


redis操作, 详细操作函数参考ioredis包

示例

$.redis.get('openid')
$.redis.set('abc', 'fdasfdsakmdsagnkgsal1mgl5reankn')

返回顶部


REG


$.reg


常用的正则表达式

示例

$.reg.int             // 验证为整数的正则
$.reg.intsingless     // 验证为正整数的正则
$.reg.float           // 验证为浮点数的正则
$.reg.tel             // 验证为电话号的正则
$.reg.mobile          // 验证为手机号的正则
$.reg.email           // 验证为电子邮箱的正则
$.reg.url             // 验证为URL地址的正则
$.reg.ip              // 验证为ip地址的正则
$.reg.card            // 验证为身份证号的正则
$.reg.postcode        // 验证为邮政编码的正则
$.reg.qq              // 验证为qq号码的正则
$.reg.english         // 验证为英文字符的正则
$.reg.chinese         // 验证为中文字符的正则
$.reg.username        // 验证为用户名(允许英文、数字、下划线,6-16位)的正则
$.reg.password        // 验证为密码(允许英文、数字、下划线,6-16位)的正则

ROUTER


$.router.getRoutes(objRouter)


导入目录下的所有路由,并合并所有路由

参数

  1. objRouter(Object):外部传入的router对象,只能由入口文件生成和传入

示例

 

返回顶部


STR


$.str.truncate(strSrc, [intLength], [strJoin])


截取字符串一定长度并拼接上一个新的字符串

参数

  1. strSrc(String):源字符串
  2. [intLength](Int):截取长度, 默认为8
  3. [strJoin](String):拼接字符串, 默认为 "..."

返回值

(String):组织好的新的字符串

示例

console.log($.str.truncate('abcdefg', 4, '...'))
// abcd...

$.str.replaceAll(strSrc, strReplace, strTarget)


替换字符串内的所有指定内容

参数

  1. strSrc(String):源字符串
  2. strReplace(String):需要替换的字符串
  3. strTarget(String):目标字符串

返回值

(String):组织好的新的字符串

示例

console.log($.str.replaceAll('abcxbzqbe', 'b', 'B'))
// aBcxBzqBe

返回顶部


TIME


$.time10([strDate])


返回10位时间戳(到秒)

参数

  1. [strDate](Object):传入的JS日期时间格式, 如果不传, 则返回当前时间点的10位时间戳

返回值

(Int):返回10位时间戳

示例

console.log($.time10())
console.log(new Date(1504140469000))
// 1504140469(当前时间戳,10位)
// 1504140469

$.time13([strDate])


返回13位时间戳(到毫秒)

参数

  1. [strDate](Object):传入的JS日期时间格式, 如果不传, 则返回当前时间点的13位时间戳

返回值

(Int):返回13位时间戳

示例

console.log($.time13())
console.log(new Date(1504140469000))
// 1504140469(当前时间戳,13位)
// 1504140469000

$.time.format(strDate, [timestamp])


格式化一个时间戳或当前时间戳为字符串日期格式

参数

  1. strStyle(String):将时间格式化后的样式,如:yyyy-mm-dd
  2. timestamp(Int):需要转换的时间戳,如果不传入,则使用当前时间的13位时间戳

返回值

(String):格式化后的日期格式

示例

/**
 * 如果传入10位时间戳和13位时间戳都可以
 */
console.log($.time.format('yyyy-mm-dd HH:MM:ss', 1504140469))
console.log($.time.format('yyyy-mm-dd HH:MM:ss', 1504140469000))
// 2017-8-31 8:47:49
// 2017-8-31 8:47:49

$.time.betweenDay([timestamp])


根据一个时间戳获取该天的开始时间戳和结束时间戳

参数

  1. [timestamp](Int):需要取某一天开始结束时间戳的标志时间戳,如果不传入,则使用当前时间的13位时间戳

返回值

(Object):返回一个对象,格式:{start: xxxxx, end: xxxxx}, 时间戳位数根据传入时间戳参数而定, 不传入的时候默认为13位时间戳

示例

console.log($.time.betweenDay(1504140469))
console.log($.time.betweenDay(1504140469000))
// {start: 1504108800, end: 1504195200}
// {start: 1504108800000, end: 1504195200000}

$.time.betweenWeek([timestamp])


根据一个时间戳获取该星期的开始时间戳和结束时间戳

参数

  1. [timestamp](Int):需要取某一周开始结束时间戳的标志时间戳,如果不传入,则使用当前时间的13位时间戳

返回值

(Object):返回一个对象,格式:{start: xxxxx, end: xxxxx}, 时间戳位数根据传入时间戳参数而定, 不传入的时候默认为13位时间戳

示例

console.log($.time.betweenWeek(1504140469))
console.log($.time.betweenWeek(1504140469000))
// {start: 1503849600, end: 1504454400}
// {start: 1503849600000, end: 1504454400000}

$.time.beweenMonth([timestamp])


根据一个时间戳获取该月的开始时间戳和结束时间戳

参数

  1. [timestamp](Int):需要取某一月开始结束时间戳的标志时间戳,如果不传入,则使用当前时间的13位时间戳

返回值

(Object):返回一个对象,格式:{start: xxxxx, end: xxxxx}, 时间戳位数根据传入时间戳参数而定, 不传入的时候默认为13位时间戳

示例

console.log($.time.beweenMonth(1504140469))
console.log($.time.beweenMonth(1504140469000))
// {start: 1501516800, end: 1504195200}
// {start: 1501516800000, end: 1504195200000}

返回顶部


UPLOAD


$.upload(ctx)


【Promise】上传操作,公开方法

参数

  1. ctx(Object):Koa2 上下文对象, fields : ctx.request.fields对象

返回值

(Array):文件上传后,可访问的路径地址, 是个数组(阿里云OSS地址)

示例

 

返回顶部


VALID


$.valid.validFunc(strCallName, objParams, objValid)


验证方法传参时,一个或多个参数是否合法

参数

  1. strCallName(String):调用此方法的方法名
  2. objParams(Object):需要验证的参数对象
  3. objValid(Object):验证条件

返回值

无返回, 如果验证出错,则直接抛出异常

示例

/**
 * 例: 验证 Time.format (strStyle, timestamp) 方法参数
 */
$.valid.validFunc('Time.format', {strStyle, timestamp}, {
  strStyle: {type: 'string'},
  timestamp: {type: 'int|undefined'}
})

$.valid.validHttp(objContext, objValid)


验证Http传参时,一个或多个参数是否合法

参数

  1. objContext(Object):需要验证的http上下文
  2. objValid(Object):验证条件

返回值

无返回, 如果验证出错,则直接抛出异常

示例

/**
 * 例: 验证 post请求'http://www.baidu.com', 参数为{name: 'xxx', age: 18}, 接收到的请求对象为objContext
 */
$.valid.validHttp(objContext, {name, age}, {
  name: {type: 'string'},
  age: {type: 'int'}
})

返回顶部


XML


$.xml.toXml(objJson)


将JSON对象转义为XML字符串

参数

  1. objJson(Object):需要转义的JSON对象

返回值

(String):转义好的XML字符串

示例

$.xml.toXml(objJson)

$.xml.toJson(strXml)


将XML字符串转义为JSON对象

参数

  1. strXml(String):需要转义为JSON对象的XML字符串

返回值

(Object):转义好的JSON对象

示例

$.xml.toJson(strXml)

返回顶部


OSS


$.aliyun.oss.put(strSrcPath, strOSSPath)


将一个本地文件上传到aliyun OSS中

参数

  1. strSrcPath(String):数据库连接配置对象
  2. strOSSPath(String):查询字符串, 可以为

返回值

(Object):从阿里云OSS返回的Json对象

示例

/**
 * 从阿里云OSS返回的Json对象示例
 *{ name: 'upload/image/ppp.jpg',
 *  url: 'http://hoss-upload.oss-cn-shanghai.aliyuncs.com/upload/image/ppp.jpg',
 *  res:
 *  { status: 200,
 *    statusCode: 200,
 *    headers:
 *      { server: 'AliyunOSS',
 *        date: 'Fri, 18 Aug 2017 08:23:15 GMT',
 *        'content-length': '0',
 *        connection: 'keep-alive',
 *        'x-oss-request-id': '5996A3F3F6545C19C54F2584',
 *        etag: '"06F82E986D2E8EDF5849ACD0179DE704"',
 *        'x-oss-hash-crc64ecma': '6023327715514683700',
 *        'content-md5': 'BvgumG0ujt9YSazQF53nBA==',
 *        'x-oss-server-time': '100' },
 *    size: 0,
 *    aborted: false,
 *    rt: 268,
 *    keepAliveSocket: false,
 *    data: <Buffer >,
 *    requestUrls: [ 'http://hoss-upload.oss-cn-shanghai.aliyuncs.com/upload/image/ppp.jpg' ],
 *    timing: null,
 *    remoteAddress: '106.14.228.191',
 *    remotePort: 80 } }
 */
$.aliyun.oss.put(strSrcPath, strOSSPath)

返回顶部

Readme

Keywords

none

Package Sidebar

Install

npm i hc-basis-node

Weekly Downloads

0

Version

0.0.9

License

ISC

Last publish

Collaborators

  • yuanxr