├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── Dockerfile ├── Readme.md ├── bash ├── install_nodejs_centos.sh └── install_nodejs_ubuntu.sh ├── config.example.json ├── core.js ├── crons.js ├── img └── screenshot.png ├── install.sh ├── logs └── readme.md ├── netease.md ├── package.json ├── plugins.js ├── public └── favicon.ico ├── routes.js ├── shrinkwrap.yaml ├── src ├── cache.js ├── controller.js ├── controllers │ ├── hello.js │ ├── netease.js │ └── status.js ├── cron.js ├── crons │ ├── countRequests.js │ ├── dumpOriginStatistics.js │ └── resetOriginStatistics.js ├── db.js ├── logger.js ├── mail.js ├── middlewares │ ├── MailError.js │ ├── countRequest.js │ ├── requestId.js │ └── responseHandler.js ├── models │ ├── behaviors │ │ └── test.js │ └── databases │ │ ├── model.js │ │ └── test.js ├── prestart.js ├── route.js └── utils.js ├── test ├── .eslintrc.json └── unit │ ├── mail.js │ └── test.js ├── update.sh ├── usage.md └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Matches multiple files with brace expansion notation 12 | # Set default charset 13 | [*.{js,py}] 14 | charset = utf-8 15 | 16 | # 4 space indentation 17 | [*.py] 18 | indent_style = space 19 | indent_size = 4 20 | 21 | # Tab indentation (no size specified) 22 | [Makefile] 23 | indent_style = tab 24 | 25 | # Indentation override for all JS under lib directory 26 | [*.js] 27 | indent_style = space 28 | indent_size = 2 29 | 30 | # Matches the exact files either package.json or .travis.yml 31 | [{package.json,.travis.yml}] 32 | indent_style = space 33 | indent_size = 2 34 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | config.json 3 | logs/*.log 4 | .nyc_output/ 5 | coverage/ 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # The base image is the latest 8.x node (LTS) 2 | FROM node:8.9.0 3 | 4 | # Init Runtime 5 | ADD . /app/ 6 | WORKDIR /app 7 | 8 | # Mount VOLUME 9 | VOLUME /hitokoto 10 | ARG NODE_ENV 11 | ENV NODE_ENV $NODE_ENV 12 | 13 | RUN npm i pnpm -g 14 | RUN pnpm install --force 15 | 16 | ENV NODE_ENV=production \ 17 | daemon=false \ 18 | silent=false \ 19 | CONFIG_FILE="" 20 | 21 | CMD node core.js --config_path "$CONFIG_FILE" 22 | 23 | # the default port for Teng-koa is exposed outside the container 24 | EXPOSE 8000 25 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Teng-koa 2 | 3 | ![alt](./img/screenshot.png) 4 | > 项目已经通过生产测试。 目前附带的 Demo 5 | * Live Demo: https://api.a632079.me/ (Hitokoto API + NeteaseCloud Music API) [使用说明](https://i.a632079.me/post/api/) 6 | 7 | * Demo: 网易云 API (基于 `simple-netease-cloud-music`) 8 | * Demo: 请求数统计 (过去一分钟,过去一小时,过去一天) 9 | 10 | > 考虑开源一言 (预计下一次更新就是开放一言的时间) 11 | 12 | 本项目是基于 Koa v2 的 RESTful API 框架实现,目前已经实现的功能: 13 | > 可以使用 `install.sh` 在 CentOS/Ubuntu 下快捷安装 Node.js 14 | > 可以使用 `update.sh` 在 Linux 下通过 Git 快捷更新 您修改后的API 15 | 16 | * 自动化加载的路由 17 | * 自动化加载的控制器 18 | * 易用的日记系统 19 | * 简单的访问统计 20 | * 更方便的中间件,插件管理 21 | * 更方便的 Cron 管理 22 | * 邮件发送模块 (提供 API服务错误时发送邮件给管理员 的功能) 23 | * 简单的 SQL ORM 实现 24 | * 简单的 Redis Cache 系统 25 | * 支持 Docker 26 | * 支持负载均衡部署 27 | 28 | 29 | ## 开始 30 | 参考 [使用说明](./usage.md) 31 | ``` 32 | $ rm -rf .git 33 | $ rm -rf img 34 | $ cp config.json.example config.json 35 | $ yarn init && git init 36 | ``` 37 | 38 | > 开始使用前, 请为 `./src/controllers/status.js` 中的 limitHost 添加您所需要的域名 (处于安全考虑, 防止泄露您的 IP) 39 | 40 | 由于项目的设计初衷是 RESTful API 的易用轮子,所以并未考虑视图,有需要的可自行集成。 41 | 如果在使用中碰到问题或者您有更好的实现,想法,欢迎联系我。 42 | 43 | ----------------------------- 44 | * Demo: Netease Cloud Music API (based on `simple-netease-cloud-music`) 45 | * Demo: Count Requests number (pastMinute, pastHour, pastDay) 46 | 47 | This Project is based on `koa v2`. This is a RESTful API framework. 48 | The features list: 49 | * auto-load routes 50 | * auto-load controllers 51 | * handy log system 52 | * handy request count 53 | * handy cron manager 54 | * handy Middlewares/plugins manager 55 | * simple & fast SQL ORM 56 | * simple & handy Redis Cache System 57 | * Mail Support (Can Send Mail While Crash) 58 | 59 | ## How to Start ? 60 | ``` 61 | $ rm -rf .git 62 | $ rm -rf img 63 | $ cp config.json.example config.json 64 | $ yarn init && git init 65 | ``` 66 | 67 | If you occur a error in use or have a better idea about it , contact me please. 68 | -------------------------------------------------------------------------------- /bash/install_nodejs_centos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Discussion, issues and change requests at: 4 | # https://github.com/nodesource/distributions 5 | # 6 | # Script to install the NodeSource Node.js 8.x repo onto an 7 | # Enterprise Linux or Fedora Core based system. 8 | # 9 | # Run as root or insert `sudo -E` before `bash`: 10 | # 11 | # curl -sL https://rpm.nodesource.com/setup_8.x | bash - 12 | # or 13 | # wget -qO- https://rpm.nodesource.com/setup_8.x | bash - 14 | # 15 | 16 | print_status() { 17 | local outp=$(echo "$1" | sed -r 's/\\n/\\n## /mg') 18 | echo 19 | echo -e "## ${outp}" 20 | echo 21 | } 22 | 23 | bail() { 24 | echo 'Error executing command, exiting' 25 | exit 1 26 | } 27 | 28 | exec_cmd_nobail() { 29 | echo "+ $1" 30 | bash -c "$1" 31 | } 32 | 33 | exec_cmd() { 34 | exec_cmd_nobail "$1" || bail 35 | } 36 | 37 | print_status "Installing the NodeSource Node.js 8.x repo..." 38 | 39 | print_status "Inspecting system..." 40 | 41 | if [ ! -x /bin/rpm ]; then 42 | print_status "\ 43 | You don't appear to be running an Enterprise Linux based \ 44 | system, please contact NodeSource at \ 45 | https://github.com/nodesource/distributions/issues if you think this \ 46 | is incorrect or would like your distribution to be considered for \ 47 | support.\ 48 | " 49 | exit 1 50 | fi 51 | 52 | ## Annotated section for auto extraction in test.sh 53 | #-check-distro-# 54 | 55 | ## Check distro and arch 56 | echo "+ rpm -q --whatprovides redhat-release || rpm -q --whatprovides centos-release || rpm -q --whatprovides cloudlinux-release || rpm -q --whatprovides sl-release" 57 | DISTRO_PKG=$(rpm -q --whatprovides redhat-release || rpm -q --whatprovides centos-release || rpm -q --whatprovides cloudlinux-release || rpm -q --whatprovides sl-release) 58 | echo "+ uname -m" 59 | UNAME_ARCH=$(uname -m) 60 | 61 | 62 | if [ "X${UNAME_ARCH}" == "Xi686" ]; then 63 | DIST_ARCH=i386 64 | elif [ "X${UNAME_ARCH}" == "Xx86_64" ]; then 65 | DIST_ARCH=x86_64 66 | else 67 | 68 | print_status "\ 69 | You don't appear to be running a supported machine architecture: ${UNAME_ARCH}. \ 70 | Please contact NodeSource at \ 71 | https://github.com/nodesource/distributions/issues if you think this is \ 72 | incorrect or would like your architecture to be considered for support. \ 73 | " 74 | exit 1 75 | 76 | fi 77 | 78 | if [[ $DISTRO_PKG =~ ^(redhat|centos|cloudlinux|sl)- ]]; then 79 | DIST_TYPE=el 80 | elif [[ $DISTRO_PKG =~ ^system-release- ]]; then # Amazon Linux 81 | DIST_TYPE=el 82 | elif [[ $DISTRO_PKG =~ ^(fedora|korora)- ]]; then 83 | DIST_TYPE=fc 84 | else 85 | 86 | print_status "\ 87 | You don't appear to be running a supported version of Enterprise Linux. \ 88 | Please contact NodeSource at \ 89 | https://github.com/nodesource/distributions/issues if you think this is \ 90 | incorrect or would like your architecture to be considered for support. \ 91 | Include your 'distribution package' name: ${DISTRO_PKG}. \ 92 | " 93 | exit 1 94 | 95 | fi 96 | 97 | if [[ $DISTRO_PKG =~ ^system-release-201[4-9]\. ]]; then #NOTE: not really future-proof 98 | 99 | # Amazon Linux, for 2014.* use el7, older versions are unknown, perhaps el6 100 | DIST_VERSION=7 101 | 102 | else 103 | 104 | ## Using the redhat-release-server-X, centos-release-X, etc. pattern 105 | ## extract the major version number of the distro 106 | DIST_VERSION=$(echo $DISTRO_PKG | sed -r 's/^[[:alpha:]]+-release(-server|-workstation)?-([0-9]+).*$/\2/') 107 | 108 | if ! [[ $DIST_VERSION =~ ^[0-9][0-9]?$ ]]; then 109 | 110 | print_status "\ 111 | Could not determine your distribution version, you may not be running a \ 112 | supported version of Enterprise Linux. \ 113 | Please contact NodeSource at \ 114 | https://github.com/nodesource/distributions/issues if you think this is \ 115 | incorrect. Include your 'distribution package' name: ${DISTRO_PKG}. \ 116 | " 117 | exit 1 118 | 119 | fi 120 | 121 | fi 122 | 123 | 124 | ## Given the distro, version and arch, construct the url for 125 | ## the appropriate nodesource-release package (it's noarch but 126 | ## we include the arch in the directory tree anyway) 127 | RELEASE_URL_VERSION_STRING="${DIST_TYPE}${DIST_VERSION}" 128 | RELEASE_URL="\ 129 | https://mirrors.ustc.edu.cn/nodesource/rpm/pub_8.x/\ 130 | ${DIST_TYPE}/\ 131 | ${DIST_VERSION}/\ 132 | ${DIST_ARCH}/\ 133 | nodesource-release-${RELEASE_URL_VERSION_STRING}-1.noarch.rpm" 134 | 135 | #-check-distro-# 136 | 137 | print_status "Confirming \"${DIST_TYPE}${DIST_VERSION}-${DIST_ARCH}\" is supported..." 138 | 139 | ## Simple fetch & fast-fail to see if the nodesource-release 140 | ## file exists for this distro/version/arch 141 | exec_cmd_nobail "curl -sLf -o /dev/null '${RELEASE_URL}'" 142 | RC=$? 143 | 144 | if [[ $RC != 0 ]]; then 145 | print_status "\ 146 | Your distribution, identified as \"${DISTRO_PKG}\", \ 147 | is not currently supported, please contact NodeSource at \ 148 | https://github.com/nodesource/distributions/issues \ 149 | if you think this is incorrect or would like your distribution to be considered for support" 150 | exit 1 151 | fi 152 | 153 | ## EPEL is needed for EL5, we don't install it if it's missing but 154 | ## we can give guidance 155 | if [ "$DIST_TYPE" == "el" ] && [ "$DIST_VERSION" == "5" ]; then 156 | 157 | print_status "Checking if EPEL is enabled..." 158 | 159 | echo "+ yum repolist enabled 2> /dev/null | grep epel" 160 | repolist=$(yum repolist enabled 2> /dev/null | grep epel) 161 | 162 | if [ "X${repolist}" == "X" ]; then 163 | print_status "Finding current EPEL release RPM..." 164 | 165 | ## We can scrape the html to find the latest epel-release (likely 5.4) 166 | epel_url="http://dl.fedoraproject.org/pub/epel/5/${DIST_ARCH}/" 167 | epel_release_view="${epel_url}repoview/epel-release.html" 168 | echo "+ curl -s $epel_release_view | grep -oE 'epel-release-[0-9\-]+\.noarch\.rpm'" 169 | epel=$(curl -s $epel_release_view | grep -oE 'epel-release-[0-9\-]+\.noarch\.rpm') 170 | if [ "X${epel}" = "X" ]; then 171 | print_status "Error: Could not find current EPEL release RPM!" 172 | exit 1 173 | fi 174 | 175 | print_status "\ 176 | The EPEL (Extra Packages for Enterprise Linux) repository is a\n\ 177 | prerequisite for installing Node.js on your operating system. Please\n\ 178 | add it and re-run this setup script.\n\ 179 | \n\ 180 | The EPEL repository RPM is available at:\n\ 181 | ${epel_url}${epel}\n\ 182 | You can try installing with: \`rpm -ivh \`\ 183 | " 184 | 185 | exit 1 186 | fi 187 | 188 | fi 189 | 190 | print_status "Downloading release setup RPM..." 191 | 192 | ## Two-step process to install the nodesource-release RPM, 193 | ## Download to a tmp file then install it directly with `rpm`. 194 | ## We don't rely on RPM's ability to fetch from HTTPS directly 195 | echo "+ mktemp" 196 | RPM_TMP=$(mktemp || bail) 197 | 198 | exec_cmd "curl -sL -o '${RPM_TMP}' '${RELEASE_URL}'" 199 | 200 | print_status "Installing release setup RPM..." 201 | 202 | ## --nosignature because nodesource-release contains the signature! 203 | exec_cmd "rpm -i --nosignature --force '${RPM_TMP}'" 204 | 205 | print_status "Cleaning up..." 206 | 207 | exec_cmd "rm -f '${RPM_TMP}'" 208 | 209 | print_status "Checking for existing installations..." 210 | 211 | ## Nasty consequences if you have an existing Node or npm package 212 | ## installed, need to inform if they are there 213 | echo "+ rpm -qa 'node|npm' | grep -v nodesource" 214 | EXISTING_NODE=$(rpm -qa 'node|npm|iojs' | grep -v nodesource) 215 | 216 | if [ "X${EXISTING_NODE}" != "X" ]; then 217 | 218 | print_status "\ 219 | Your system appears to already have Node.js installed from an alternative source.\n\ 220 | Run \`\033[1myum remove -y nodejs npm\033[22m\` (as root) to remove these first.\ 221 | " 222 | 223 | fi 224 | 225 | print_status "\ 226 | Run \`\033[1myum install -y nodejs\033[22m\` (as root) to install Node.js 8.x and npm.\n\ 227 | You may also need development tools to build native addons:\n\ 228 | \`yum install -y gcc-c++ make\`\ 229 | " 230 | 231 | ## Alternative to install dev tools: `yum groupinstall 'Development Tools' 232 | 233 | exit 0 234 | -------------------------------------------------------------------------------- /bash/install_nodejs_ubuntu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Discussion, issues and change requests at: 4 | # https://github.com/nodesource/distributions 5 | # 6 | # Script to install the NodeSource Node.js v8.x LTS Carbon repo onto a 7 | # Debian or Ubuntu system. 8 | # 9 | # Run as root or insert `sudo -E` before `bash`: 10 | # 11 | # curl -sL https://deb.nodesource.com/setup_8.x | bash - 12 | # or 13 | # wget -qO- https://deb.nodesource.com/setup_8.x | bash - 14 | # 15 | 16 | export DEBIAN_FRONTEND=noninteractive 17 | SCRSUFFIX="_8.x" 18 | NODENAME="Node.js v8.x LTS Carbon" 19 | NODEREPO="node_8.x" 20 | NODEPKG="nodejs" 21 | 22 | print_status() { 23 | echo 24 | echo "## $1" 25 | echo 26 | } 27 | 28 | if test -t 1; then # if terminal 29 | ncolors=$(which tput > /dev/null && tput colors) # supports color 30 | if test -n "$ncolors" && test $ncolors -ge 8; then 31 | termcols=$(tput cols) 32 | bold="$(tput bold)" 33 | underline="$(tput smul)" 34 | standout="$(tput smso)" 35 | normal="$(tput sgr0)" 36 | black="$(tput setaf 0)" 37 | red="$(tput setaf 1)" 38 | green="$(tput setaf 2)" 39 | yellow="$(tput setaf 3)" 40 | blue="$(tput setaf 4)" 41 | magenta="$(tput setaf 5)" 42 | cyan="$(tput setaf 6)" 43 | white="$(tput setaf 7)" 44 | fi 45 | fi 46 | 47 | print_bold() { 48 | title="$1" 49 | text="$2" 50 | 51 | echo 52 | echo "${red}================================================================================${normal}" 53 | echo "${red}================================================================================${normal}" 54 | echo 55 | echo -e " ${bold}${yellow}${title}${normal}" 56 | echo 57 | echo -en " ${text}" 58 | echo 59 | echo "${red}================================================================================${normal}" 60 | echo "${red}================================================================================${normal}" 61 | } 62 | 63 | bail() { 64 | echo 'Error executing command, exiting' 65 | exit 1 66 | } 67 | 68 | exec_cmd_nobail() { 69 | echo "+ $1" 70 | bash -c "$1" 71 | } 72 | 73 | exec_cmd() { 74 | exec_cmd_nobail "$1" || bail 75 | } 76 | 77 | node_deprecation_warning() { 78 | if [[ "X${NODENAME}" == "Xio.js v1.x" || 79 | "X${NODENAME}" == "Xio.js v2.x" || 80 | "X${NODENAME}" == "Xio.js v3.x" || 81 | "X${NODENAME}" == "XNode.js v5.x" ]]; then 82 | 83 | print_bold \ 84 | " DEPRECATION WARNING " "\ 85 | ${bold}${NODENAME} is no longer actively supported!${normal} 86 | 87 | ${bold}You will not receive security or critical stability updates${normal} for this version. 88 | 89 | You should migrate to a supported version of Node.js as soon as possible. 90 | Use the installation script that corresponds to the version of Node.js you 91 | wish to install. e.g. 92 | 93 | * ${green}https://deb.nodesource.com/setup_4.x 鈥� Node.js v4 LTS \"Argon\"${normal} (recommended) 94 | * ${green}https://deb.nodesource.com/setup_6.x 鈥� Node.js v6 Current${normal} 95 | 96 | Please see ${bold}https://github.com/nodejs/LTS/${normal} for details about which version 97 | may be appropriate for you. 98 | 99 | The ${bold}NodeSource${normal} Node.js Linux distributions GitHub repository contains 100 | information about which versions of Node.js and which Linux distributions 101 | are supported and how to use the install scripts. 102 | ${bold}https://github.com/nodesource/distributions${normal} 103 | " 104 | echo 105 | echo "Continuing in 10 seconds ..." 106 | echo 107 | sleep 10 108 | 109 | elif [ "X${NODENAME}" == "XNode.js v0.10" ]; then 110 | 111 | print_bold \ 112 | " NODE.JS v0.10 DEPRECATION WARNING " "\ 113 | Node.js v0.10 will cease to be actively supported in ${bold}October 2016${normal}. 114 | 115 | This means you will not continue to receive security or critical stability 116 | updates for this version of Node.js beyond that time. 117 | 118 | You should begin migration to a newer version of Node.js as soon as 119 | possible. Use the installation script that corresponds to the version of 120 | Node.js you wish to install. e.g. 121 | 122 | * ${green}https://deb.nodesource.com/setup_4.x 鈥� Node.js v4 LTS \"Argon\"${normal} (recommended) 123 | * ${green}https://deb.nodesource.com/setup_6.x 鈥� Node.js v6 Current${normal} 124 | 125 | Please see ${bold}https://github.com/nodejs/LTS/${normal} for details about which version 126 | may be appropriate for you. 127 | 128 | The ${bold}NodeSource${normal} Node.js Linux distributions GitHub repository contains 129 | information about which versions of Node.js and which Linux distributions 130 | are supported and how to use the install scripts. 131 | ${bold}https://github.com/nodesource/distributions${normal} 132 | " 133 | 134 | echo 135 | echo "Continuing in 5 seconds ..." 136 | echo 137 | sleep 5 138 | 139 | elif [ "X${NODENAME}" == "XNode.js v0.12" ]; then 140 | 141 | print_bold \ 142 | " NODE.JS v0.12 DEPRECATION WARNING " "\ 143 | Node.js v0.12 will cease to be actively supported ${bold}at the end of 2016${normal}. 144 | 145 | This means you will not continue to receive security or critical stability 146 | updates for this version of Node.js beyond that time. 147 | 148 | You should begin migration to a newer version of Node.js as soon as 149 | possible. Use the installation script that corresponds to the version of 150 | Node.js you wish to install. e.g. 151 | 152 | * ${green}https://deb.nodesource.com/setup_4.x 鈥� Node.js v4 LTS \"Argon\"${normal} (recommended) 153 | * ${green}https://deb.nodesource.com/setup_6.x 鈥� Node.js v6 Current${normal} 154 | 155 | Please see ${bold}https://github.com/nodejs/LTS/${normal} for details about which version 156 | may be appropriate for you. 157 | 158 | The ${bold}NodeSource${normal} Node.js Linux distributions GitHub repository contains 159 | information about which versions of Node.js and which Linux distributions 160 | are supported and how to use the install scripts. 161 | ${bold}https://github.com/nodesource/distributions${normal} 162 | " 163 | 164 | echo 165 | echo "Continuing in 3 seconds ..." 166 | echo 167 | sleep 3 168 | 169 | fi 170 | } 171 | 172 | script_deprecation_warning() { 173 | if [ "X${SCRSUFFIX}" == "X" ]; then 174 | print_bold \ 175 | " SCRIPT DEPRECATION WARNING " "\ 176 | This script, located at ${bold}https://deb.nodesource.com/setup${normal}, used to 177 | install Node.js v0.10, is being deprecated and will eventually be made 178 | inactive. 179 | 180 | You should use the script that corresponds to the version of Node.js you 181 | wish to install. e.g. 182 | 183 | * ${green}https://deb.nodesource.com/setup_4.x 鈥� Node.js v4 LTS \"Argon\"${normal} (recommended) 184 | * ${green}https://deb.nodesource.com/setup_6.x 鈥� Node.js v6 Current${normal} 185 | 186 | Please see ${bold}https://github.com/nodejs/LTS/${normal} for details about which version 187 | may be appropriate for you. 188 | 189 | The ${bold}NodeSource${normal} Node.js Linux distributions GitHub repository contains 190 | information about which versions of Node.js and which Linux distributions 191 | are supported and how to use the install scripts. 192 | ${bold}https://github.com/nodesource/distributions${normal} 193 | " 194 | 195 | echo 196 | echo "Continuing in 10 seconds (press Ctrl-C to abort) ..." 197 | echo 198 | sleep 10 199 | fi 200 | } 201 | 202 | setup() { 203 | 204 | script_deprecation_warning 205 | 206 | print_status "Installing the NodeSource ${NODENAME} repo..." 207 | 208 | if $(uname -m | grep -Eq ^armv6); then 209 | print_status "You appear to be running on ARMv6 hardware. Unfortunately this is not currently supported by the NodeSource Linux distributions. Please use the 'linux-armv6l' binary tarballs available directly from nodejs.org for Node.js v4 and later." 210 | exit 1 211 | fi 212 | 213 | PRE_INSTALL_PKGS="" 214 | 215 | # Check that HTTPS transport is available to APT 216 | # (Check snaked from: https://get.docker.io/ubuntu/) 217 | 218 | if [ ! -e /usr/lib/apt/methods/https ]; then 219 | PRE_INSTALL_PKGS="${PRE_INSTALL_PKGS} apt-transport-https" 220 | fi 221 | 222 | if [ ! -x /usr/bin/lsb_release ]; then 223 | PRE_INSTALL_PKGS="${PRE_INSTALL_PKGS} lsb-release" 224 | fi 225 | 226 | if [ ! -x /usr/bin/curl ] && [ ! -x /usr/bin/wget ]; then 227 | PRE_INSTALL_PKGS="${PRE_INSTALL_PKGS} curl" 228 | fi 229 | 230 | # Populating Cache 231 | print_status "Populating apt-get cache..." 232 | exec_cmd 'apt-get update' 233 | 234 | if [ "X${PRE_INSTALL_PKGS}" != "X" ]; then 235 | print_status "Installing packages required for setup:${PRE_INSTALL_PKGS}..." 236 | # This next command needs to be redirected to /dev/null or the script will bork 237 | # in some environments 238 | exec_cmd "apt-get install -y${PRE_INSTALL_PKGS} > /dev/null 2>&1" 239 | fi 240 | 241 | IS_PRERELEASE=$(lsb_release -d | grep 'Ubuntu .*development' >& /dev/null; echo $?) 242 | if [[ $IS_PRERELEASE -eq 0 ]]; then 243 | print_status "Your distribution, identified as \"$(lsb_release -d -s)\", is a pre-release version of Ubuntu. NodeSource does not maintain official support for Ubuntu versions until they are formally released. You can try using the manual installation instructions available at https://github.com/nodesource/distributions and use the latest supported Ubuntu version name as the distribution identifier, although this is not guaranteed to work." 244 | exit 1 245 | fi 246 | 247 | DISTRO=$(lsb_release -c -s) 248 | 249 | check_alt() { 250 | if [ "X${DISTRO}" == "X${2}" ]; then 251 | echo 252 | echo "## You seem to be using ${1} version ${DISTRO}." 253 | echo "## This maps to ${3} \"${4}\"... Adjusting for you..." 254 | DISTRO="${4}" 255 | fi 256 | } 257 | 258 | check_alt "Kali" "sana" "Debian" "jessie" 259 | check_alt "Kali" "kali-rolling" "Debian" "jessie" 260 | check_alt "Sparky Linux" "Nibiru" "Debian" "buster" 261 | check_alt "Linux Mint" "maya" "Ubuntu" "precise" 262 | check_alt "Linux Mint" "qiana" "Ubuntu" "trusty" 263 | check_alt "Linux Mint" "rafaela" "Ubuntu" "trusty" 264 | check_alt "Linux Mint" "rebecca" "Ubuntu" "trusty" 265 | check_alt "Linux Mint" "rosa" "Ubuntu" "trusty" 266 | check_alt "Linux Mint" "sarah" "Ubuntu" "xenial" 267 | check_alt "Linux Mint" "serena" "Ubuntu" "xenial" 268 | check_alt "Linux Mint" "sonya" "Ubuntu" "xenial" 269 | check_alt "Linux Mint" "sylvia" "Ubuntu" "xenial" 270 | check_alt "LMDE" "betsy" "Debian" "jessie" 271 | check_alt "elementaryOS" "luna" "Ubuntu" "precise" 272 | check_alt "elementaryOS" "freya" "Ubuntu" "trusty" 273 | check_alt "elementaryOS" "loki" "Ubuntu" "xenial" 274 | check_alt "Trisquel" "toutatis" "Ubuntu" "precise" 275 | check_alt "Trisquel" "belenos" "Ubuntu" "trusty" 276 | check_alt "Trisquel" "flidas" "Ubuntu" "xenial" 277 | check_alt "BOSS" "anokha" "Debian" "wheezy" 278 | check_alt "bunsenlabs" "bunsen-hydrogen" "Debian" "jessie" 279 | check_alt "Tanglu" "chromodoris" "Debian" "jessie" 280 | check_alt "Devuan" "ceres" "Debian" "sid" 281 | check_alt "PureOS" "green" "Debian" "sid" 282 | 283 | if [ "X${DISTRO}" == "Xdebian" ]; then 284 | print_status "Unknown Debian-based distribution, checking /etc/debian_version..." 285 | NEWDISTRO=$([ -e /etc/debian_version ] && cut -d/ -f1 < /etc/debian_version) 286 | if [ "X${NEWDISTRO}" == "X" ]; then 287 | print_status "Could not determine distribution from /etc/debian_version..." 288 | else 289 | DISTRO=$NEWDISTRO 290 | print_status "Found \"${DISTRO}\" in /etc/debian_version..." 291 | fi 292 | fi 293 | 294 | print_status "Confirming \"${DISTRO}\" is supported..." 295 | 296 | if [ -x /usr/bin/curl ]; then 297 | exec_cmd_nobail "curl -sLf -o /dev/null 'https://deb.nodesource.com/${NODEREPO}/dists/${DISTRO}/Release'" 298 | RC=$? 299 | else 300 | exec_cmd_nobail "wget -qO /dev/null -o /dev/null 'https://deb.nodesource.com/${NODEREPO}/dists/${DISTRO}/Release'" 301 | RC=$? 302 | fi 303 | 304 | if [[ $RC != 0 ]]; then 305 | print_status "Your distribution, identified as \"${DISTRO}\", is not currently supported, please contact NodeSource at https://github.com/nodesource/distributions/issues if you think this is incorrect or would like your distribution to be considered for support" 306 | exit 1 307 | fi 308 | 309 | if [ -f "/etc/apt/sources.list.d/chris-lea-node_js-$DISTRO.list" ]; then 310 | print_status 'Removing Launchpad PPA Repository for NodeJS...' 311 | 312 | exec_cmd_nobail 'add-apt-repository -y -r ppa:chris-lea/node.js' 313 | exec_cmd "rm -f /etc/apt/sources.list.d/chris-lea-node_js-${DISTRO}.list" 314 | fi 315 | 316 | print_status 'Adding the NodeSource signing key to your keyring...' 317 | 318 | if [ -x /usr/bin/curl ]; then 319 | exec_cmd 'curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -' 320 | else 321 | exec_cmd 'wget -qO- https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -' 322 | fi 323 | 324 | print_status "Creating apt sources list file for the NodeSource ${NODENAME} repo..." 325 | 326 | exec_cmd "echo 'deb https://mirrors.ustc.edu.cn/nodesource/deb/${NODEREPO} ${DISTRO} main' > /etc/apt/sources.list.d/nodesource.list" 327 | exec_cmd "echo 'deb-src https://mirrors.ustc.edu.cn/nodesource/deb/${NODEREPO} ${DISTRO} main' >> /etc/apt/sources.list.d/nodesource.list" 328 | 329 | print_status 'Running `apt-get update` for you...' 330 | 331 | exec_cmd 'apt-get update' 332 | 333 | node_deprecation_warning 334 | 335 | print_status "Run \`apt-get install ${NODEPKG}\` (as root) to install ${NODENAME} and npm" 336 | 337 | } 338 | 339 | ## Defer setup until we have the complete script 340 | setup 341 | -------------------------------------------------------------------------------- /config.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "url": "", 4 | "api_name": "", 5 | "server": { 6 | "host": "127.0.0.1", 7 | "port": "8000" 8 | }, 9 | "mail": { 10 | "type": "smtp", 11 | "host": "", 12 | "username": "", 13 | "password": "", 14 | "port": "", 15 | "encrypt": "ssl" 16 | }, 17 | "database": "mysql", 18 | "mysql": { 19 | "host": "127.0.0.1", 20 | "database": "", 21 | "username": "", 22 | "password": "", 23 | "port": "" 24 | }, 25 | "redis": { 26 | "host": "127.0.0.1", 27 | "port": 6379, 28 | "password": "", 29 | "database": 0 30 | }, 31 | "log_level": "info" 32 | } 33 | -------------------------------------------------------------------------------- /core.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Import Packages 4 | const winston = require('winston') 5 | const nconf = require('nconf') 6 | const path = require('path') 7 | const colors = require('colors/safe') 8 | const Koa = require('koa') // Koa v2 9 | // const mail = require('./src/mail') 10 | 11 | // Read Command 12 | let configFile 13 | const paramsArray = process.argv.slice(2) 14 | if (paramsArray.length > 1) { 15 | // exist params 16 | if (paramsArray[0] === '--config_file') { 17 | configFile = path.join('./', paramsArray[1]) 18 | } 19 | } 20 | 21 | // PreStart 22 | const preStart = require('./src/prestart') 23 | preStart.load(configFile) 24 | 25 | // Use blubird promise 26 | global.Promise = require('bluebird') 27 | 28 | // Register Server 29 | const app = new Koa() 30 | 31 | // Load CronJob 32 | const cron = require('./src/cron') 33 | cron.load() 34 | 35 | // Register Middlewares (Plugins) 36 | async function registerMiddlewares () { 37 | try { 38 | const middlewares = require('./plugins') 39 | await middlewares.map((middleware, index, input) => { 40 | app.use(middleware) 41 | }) 42 | winston.verbose('All Plugins Load done.') 43 | } catch (e) { 44 | winston.error(e) 45 | // mail.error(e) 46 | process.exit(1) 47 | } 48 | } 49 | 50 | // Load Route 51 | async function registerRoutes (routes) { 52 | try { 53 | await routes.then(router => { 54 | app 55 | .use(router.routes()) 56 | .use(router.allowedMethods()) 57 | }) 58 | .catch(err => { 59 | winston.error(err) 60 | // mail.error(err) 61 | process.exit(1) 62 | }) 63 | winston.verbose('All Routes Load done.') 64 | } catch (e) { 65 | winston.error(e) 66 | // mail.error(e) 67 | process.exit(1) 68 | } 69 | } 70 | 71 | // Start Server 72 | async function start () { 73 | try { 74 | await registerMiddlewares() 75 | const Routes = require('./src/route') 76 | await registerRoutes(new Routes()) 77 | await app.listen(nconf.get('server:port')) 78 | } catch (e) { 79 | winston.error(e) 80 | // mail.error(e) 81 | process.exit(1) 82 | } 83 | } 84 | start() 85 | winston.info(colors.green('Server is started. Listening on Port:' + nconf.get('server:port'))) 86 | -------------------------------------------------------------------------------- /crons.js: -------------------------------------------------------------------------------- 1 | // Register Cron Event 2 | module.exports = (cron) => { 3 | // return true 4 | return [ 5 | // Register cron 6 | cron.countRequests 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /img/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a632079/teng-koa/476deb95a15c353805e05aaa03333a4924d3ef58/img/screenshot.png -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | os=`uname -a` 3 | os_check_centos=".*el7.*" 4 | os_check_ubuntu="*Ubuntu*" 5 | print_status() { 6 | local outp=$(echo "$1" | sed -r 's/\\n/\\n## /mg') 7 | echo 8 | echo -e "## ${outp}" 9 | echo 10 | } 11 | bail() { 12 | echo 'Error executing command, exiting' 13 | exit 1 14 | } 15 | exec_cmd_nobail() { 16 | echo "+ $1" 17 | bash -c "$1" 18 | } 19 | 20 | exec_cmd() { 21 | exec_cmd_nobail "$1" || bail 22 | } 23 | 24 | if [[ "$os" =~ $os_check_centos ]]; then 25 | # Install Dependence 26 | exec_cmd "cat bash/install_nodejs_centos.sh | sudo bash -" 27 | exec_cmd "yum update" 28 | exec_cmd "yum install -y nodejs redis" 29 | exec_cmd "npm config set registry https://registry.npm.taobao.org/" 30 | exec_cmd "npm i -g pnpm pm2" 31 | exec_cmd "pnpm i" 32 | print_status "Install Done!" 33 | elif [[ "$os" =~ $os_check_ubuntu ]]; then 34 | exec_cmd "cat bash/install_nodejs_ubuntu.sh | sudo bash -" 35 | exec_cmd "apt update && apt upgrade" 36 | exec_cmd "apt install -y nodejs redis" 37 | exec_cmd "npm config set registry https://registry.npm.taobao.org/" 38 | exec_cmd "npm i -g pnpm pm2" 39 | exec_cmd "pnpm i" 40 | print_status "Install Done!" 41 | else 42 | echo "not support system! ${os}" 43 | exit 1 44 | fi 45 | -------------------------------------------------------------------------------- /logs/readme.md: -------------------------------------------------------------------------------- 1 | # Logs 2 | This Dir is to save `winston` logs. 3 | -------------------------------------------------------------------------------- /netease.md: -------------------------------------------------------------------------------- 1 | #### 网易云 2 | 接口: `https://v1.hitokoto.cn/nm/` 3 | 4 | > 目前该接口已开启缓存服务 (开启检测的概要查询缓存 7 天, 其余数据缓存 2 小时) 5 | > 目前 API 正在逐步更新, 由 Linux API -> Web API 以增加 `offset` 和 `limit` 参数 6 | 7 | 支持以下 ~~8~~ 16 个功能: 8 | * 搜索曲目 - `https://v1.hitokoto.cn/nm/search/:keyword` 9 | * 参数: 10 | * 种类 - `type`: 11 | * 专辑: `ALBUM` 12 | * 歌手: `ARTIST` 13 | * 电台: `DJ` 14 | * 歌词: `LYRIC` 15 | * 视频: `MV` 16 | * 歌单: `PLAYLIST` 17 | * 歌曲: `SONG` (默认) 18 | * 用户: `USER` 19 | * 偏移量 - `offset` 20 | * 限制量 - `limit` 21 | * 一次返回多少结果 22 | * 调用示例: `https://v1.hitokoto.cn/nm/search/海阔天空?type=SONG&offset=0&limit=30` 23 | * 获得歌单 - `https://v1.hitokoto.cn/nm/playlist/:id` 24 | * 获得曲图 - `https://v1.hitokoto.cn/nm/picture/:id/:height?` 25 | * 获得歌手 - `https://v1.hitokoto.cn/nm/artist/:id` 26 | * 获得专辑 - `https://v1.hitokoto.cn/nm/album/:id` 27 | * 获得歌词 - `https://v1.hitokoto.cn/nm/lyric/:id` 28 | * 获得歌曲 - `https://v1.hitokoto.cn/nm/url/:id` 29 | * 获得细节 - `https://v1.hitokoto.cn/nm/detail/:id` 30 | * 获得概要 - `https://v1.hitokoto.cn/nm/summary/:id` 31 | * 启用歌词 - `https://v1.hitokoto.cn/nm/summary/:id?lyric=true` 32 | * 关闭检测 - `https://v1.hitokoto.cn/nm/summary/:id?quick=true` 33 | * 默认开启检测, 目的是筛除无法播放的歌曲(未返回 URL) 34 | * 优化结果 - `https://v1.hitokoto.cn/nm/summary/:id?common=true` 35 | * 调用示例 - `https://v1.hitokoto.cn/nm/summary/28391863,22640061?common=true&lyric=true&quick=true` 36 | * 重定向 37 | * 歌曲 - `https://v1.hitokoto.cn/nm/redirect/music/:id` 38 | * ~~图片 - `https://v1.hitokoto.cn/nm/redirect/picture/:id`~~ 因为图片地址不改变,所以移除该 API 39 | * 播放记录 - `https://v1.hitokoto.cn/nm/record/:uid` 40 | * 获取周记录 - `https://v1.hitokoto.cn/nm/record/:uid?weekly=true` 41 | * 歌曲评论 - `https://v1.hitokoto.cn/nm/comment/music/:id` 42 | * 可选参数 - `offset` `limit` 43 | * 调用示例 - `https://v1.hitokoto.cn/nm/comment/music/28391863?offset=0&limit=100` 44 | * 视频链接 - `https://v1.hitokoto.cn/nm/url/mv/:mvid` 45 | * 受上级 SDK 影响, 暂时 302 至另一个接口 46 | * 用户电台 - `https://v1.hitokoto.cn/nm/user/dj/:uid` 47 | * 参数 - `offset` `limit` 48 | * 调用示例 - `https://v1.hitokoto.cn/nm/user/dj/91239965?limit=30&offset=0` 49 | * 电台节目 - `https://v1.hitokoto.cn/nm/dj/:rid` 50 | * 参数 - `offset` `limit` 51 | * 调用示例 - `https://v1.hitokoto.cn/nm/dj/336355127?limit=30&offset=1` 52 | * 电台细节 - `https://v1.hitokoto.cn/nm/dj/detail/:rid` 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "teng-koa", 3 | "version": "0.0.5", 4 | "description": "Simple & fast RESTful API Framework.", 5 | "main": "core.js", 6 | "engines": { 7 | "node": ">=8" 8 | }, 9 | "scripts": { 10 | "start": "node core --harmony", 11 | "watch": "supervisor --watch \".\" --extensions \"js,json\" --exec \"node\" --harmony -- \"core.js\"", 12 | "mocha": "mocha test/unit", 13 | "test": "nyc --reporter=html --reporter=text-summary mocha test\\unit", 14 | "coverall": "nyc report --reporter=text-lcov | coveralls && rm -r coverage" 15 | }, 16 | "repository": "https://github.com/a632079/teng-koa", 17 | "author": "a632079 ", 18 | "license": "MIT", 19 | "private": true, 20 | "devDependencies": { 21 | "chai": "^4.1.2", 22 | "eslint": "^4.16.0", 23 | "eslint-config-standard": "^11.0.0-beta.0", 24 | "eslint-plugin-import": "^2.8.0", 25 | "eslint-plugin-node": "^5.2.1", 26 | "eslint-plugin-promise": "^3.6.0", 27 | "eslint-plugin-standard": "^3.0.1", 28 | "koa-livereload": "^0.2.0", 29 | "mocha": "^5.0.0", 30 | "nyc": "^11.4.1", 31 | "supervisor": "^0.12.0" 32 | }, 33 | "dependencies": { 34 | "async": "^2.6.0", 35 | "axios": "^0.17.1", 36 | "bluebird": "^3.5.1", 37 | "bytes": "^3.0.0", 38 | "chalk": "^2.3.0", 39 | "colors": "^1.1.2", 40 | "cron": "^1.3.0", 41 | "humanize-number": "^0.0.2", 42 | "iconv-lite": "^0.4.19", 43 | "kcors": "^2.2.1", 44 | "koa": "^2.4.1", 45 | "koa-bodyparser": "^4.2.0", 46 | "koa-compress": "^2.0.0", 47 | "koa-favicon": "^2.0.0", 48 | "koa-helmet": "^3.3.0", 49 | "koa-json": "^2.0.2", 50 | "koa-jsonp": "^2.0.2", 51 | "koa-query-pretty": "^0.3.0", 52 | "koa-router": "^7.3.0", 53 | "lodash": "^4.17.4", 54 | "lodash.compact": "^3.0.1", 55 | "lodash.curry": "^4.1.1", 56 | "mysql2": "^1.5.1", 57 | "nconf": "^0.10.0", 58 | "netease-music-sdk": "^0.3.1", 59 | "nodemailer": "^4.4.2", 60 | "passthrough-counter": "^1.0.0", 61 | "pify": "^3.0.0", 62 | "redis": "^2.8.0", 63 | "sequelize": "^4.32.2", 64 | "simple-netease-cloud-music": "^0.3.4", 65 | "uuid": "^3.2.1", 66 | "winston": "^2.4.0" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /plugins.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | module.exports = [ 4 | // MiddleWares 5 | require('./src/middlewares/requestId')(), 6 | require('./src/middlewares/responseHandler')(), 7 | require('./src/middlewares/countRequest')(), 8 | 9 | // Mail Error 10 | require('./src/middlewares/MailError')(), 11 | 12 | // Basic Plugins 13 | require('koa-helmet')(), 14 | require('koa-query-pretty')(), 15 | require('koa-json')(), 16 | require('koa-jsonp')(), 17 | require('koa-bodyparser')({ 18 | enableTypes: ['json', 'form'], 19 | formLimit: '10mb', 20 | jsonLimit: '10mb' 21 | }), 22 | require('kcors')({ 23 | origin: '*', 24 | allowMethods: ['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH'], 25 | exposeHeaders: ['X-Request-Id'] 26 | }), 27 | require('koa-favicon')(path.join(__dirname, './public/favicon.ico')), 28 | require('koa-compress')({ 29 | filter: (contentType) => { 30 | return /text/i.test(contentType) 31 | }, 32 | threshold: 2048, 33 | flush: require('zlib').Z_SYNC_FLUSH 34 | }), 35 | require('./src/logger')(), 36 | 37 | // Dev Plugins 38 | require('koa-livereload')() 39 | ] 40 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a632079/teng-koa/476deb95a15c353805e05aaa03333a4924d3ef58/public/favicon.ico -------------------------------------------------------------------------------- /routes.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = (router, controller) => { 3 | // Route Map 4 | router.get('/', async (ctx, next) => { 5 | ctx.body = { 6 | message: 'Hello World', 7 | ts: Date.now() 8 | } 9 | }) 10 | 11 | router.get('/test', async ctx => { 12 | const nconf = require('nconf') 13 | const os = require('os') 14 | let memoryUsage = 0 15 | for (let v of Object.values(process.memoryUsage())) { 16 | memoryUsage += parseInt(v) 17 | } 18 | memoryUsage = memoryUsage / (1024 * 1024) 19 | ctx.body = { 20 | header: ctx.headers, 21 | host: ctx.request.host, 22 | server_id: nconf.get('api_name'), 23 | server_status: { 24 | memory: { 25 | totol: os.totalmem() / (1024 * 1024), 26 | free: os.freemem() / (1024 * 1024), 27 | usage: memoryUsage 28 | }, 29 | cpu: os.cpus(), 30 | load: os.loadavg() 31 | }, 32 | hostname: ctx.request.hostname, 33 | URL: ctx.request.URL, 34 | url: ctx.request.url, 35 | origin: ctx.request.origin, 36 | originalUrl: ctx.request.originalUrl, 37 | queryParams: ctx.query, 38 | queryLength: ctx.query && ctx.query.c ? ctx.query.c.length : '', 39 | now: new Date().toUTCString() 40 | } 41 | }) 42 | router.get('/helloWorld', controller.hello.index) 43 | router.get('/status', controller.status) 44 | 45 | // Netease API 46 | router.get('/nm/search/:keyword', controller.netease.search) 47 | router.get('/nm/playlist/:id', controller.netease.playlist) 48 | router.get('/nm/picture/:id/:height?', controller.netease.picture) 49 | router.get('/nm/artist/:id', controller.netease.artist) 50 | router.get('/nm/album/:id', controller.netease.album) 51 | router.get('/nm/lyric/:id', controller.netease.lyric) 52 | router.get('/nm/url/:id', controller.netease.url) 53 | router.get('/nm/detail/:id', controller.netease.detail) 54 | router.get('/nm/summary/:id', controller.netease.summary) 55 | router.get('/nm/redirect/music/:id', controller.netease.redirect) 56 | router.get('/nm/record/:uid', controller.netease.record) 57 | router.get('/nm/comment/music/:id', controller.netease.musicComment) 58 | router.get('/nm/url/mv/:mvid', async (ctx) => { 59 | ctx.redirect(301, `/nm/mv/${ctx.params.mvid}`) 60 | }) 61 | router.get('/nm/mv/:mvid', controller.netease.mv) 62 | // router.get('/nm/dj/program/detail/:pid', controller.netease.djProgramInfo) 63 | router.get('/nm/user/dj/:uid', controller.netease.userDj) 64 | router.get('/nm/dj/:rid', controller.netease.djProgram) 65 | router.get('/nm/dj/detail/:rid', controller.netease.djDetail) 66 | 67 | return router 68 | } 69 | -------------------------------------------------------------------------------- /shrinkwrap.yaml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | async: 2.6.0 3 | axios: 0.17.1 4 | bluebird: 3.5.1 5 | bytes: 3.0.0 6 | chalk: 2.3.1 7 | colors: 1.1.2 8 | cron: 1.3.0 9 | humanize-number: 0.0.2 10 | iconv-lite: 0.4.23 11 | kcors: 2.2.1 12 | koa: 2.5.0 13 | koa-bodyparser: 4.2.0 14 | koa-compress: 2.0.0 15 | koa-favicon: 2.0.0 16 | koa-helmet: 3.3.0 17 | koa-json: 2.0.2 18 | koa-jsonp: 2.0.2 19 | koa-query-pretty: 0.3.0 20 | koa-router: 7.4.0 21 | lodash: 4.17.5 22 | lodash.compact: 3.0.1 23 | lodash.curry: 4.1.1 24 | mysql2: 1.5.2 25 | nconf: 0.10.0 26 | netease-music-sdk: 0.3.1 27 | nodemailer: 4.4.2 28 | passthrough-counter: 1.0.0 29 | pify: 3.0.0 30 | redis: 2.8.0 31 | sequelize: 4.33.4 32 | simple-netease-cloud-music: 0.3.4 33 | uuid: 3.2.1 34 | winston: 2.4.0 35 | devDependencies: 36 | chai: 4.1.2 37 | eslint: 4.18.0 38 | eslint-config-standard: 11.0.0 39 | eslint-plugin-import: 2.8.0 40 | eslint-plugin-node: 5.2.1 41 | eslint-plugin-promise: 3.6.0 42 | eslint-plugin-standard: 3.0.1 43 | koa-livereload: 0.2.0 44 | mocha: 5.0.1 45 | nyc: 11.4.1 46 | supervisor: 0.12.0 47 | packages: 48 | /@types/geojson/1.0.6: 49 | dev: false 50 | resolution: 51 | integrity: sha1-PgKXJyjGkkjCrwjWCkjLuGgP/98= 52 | tarball: 'http://registry.npm.taobao.org/@types/geojson/download/@types/geojson-1.0.6.tgz' 53 | /@types/node/9.4.6: 54 | dev: false 55 | resolution: 56 | integrity: sha1-2Bdthk7kh1PQU3g+TkY67Ia42C4= 57 | tarball: 'http://registry.npm.taobao.org/@types/node/download/@types/node-9.4.6.tgz' 58 | /accepts/1.3.4: 59 | dependencies: 60 | mime-types: 2.1.18 61 | negotiator: 0.6.1 62 | engines: 63 | node: '>= 0.6' 64 | resolution: 65 | integrity: sha1-hiRnWMfdbSGmR0/whKR0DsBesh8= 66 | tarball: 'http://registry.npm.taobao.org/accepts/download/accepts-1.3.4.tgz' 67 | /acorn-jsx/3.0.1: 68 | dependencies: 69 | acorn: 3.3.0 70 | dev: true 71 | resolution: 72 | integrity: sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s= 73 | tarball: 'http://registry.npm.taobao.org/acorn-jsx/download/acorn-jsx-3.0.1.tgz' 74 | /acorn/3.3.0: 75 | dev: true 76 | engines: 77 | node: '>=0.4.0' 78 | resolution: 79 | integrity: sha1-ReN/s56No/JbruP/U2niu18iAXo= 80 | tarball: 'http://registry.npm.taobao.org/acorn/download/acorn-3.3.0.tgz' 81 | /acorn/5.4.1: 82 | dev: true 83 | engines: 84 | node: '>=0.4.0' 85 | resolution: 86 | integrity: sha1-/cWNnRf0pOmNEC3tgmqbl1kSUQI= 87 | tarball: 'http://registry.npm.taobao.org/acorn/download/acorn-5.4.1.tgz' 88 | /ajv-keywords/2.1.1/ajv@5.5.2: 89 | dependencies: 90 | ajv: 5.5.2 91 | dev: true 92 | id: registry.npm.taobao.org/ajv-keywords/2.1.1 93 | peerDependencies: 94 | ajv: ^5.0.0 95 | resolution: 96 | integrity: sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I= 97 | tarball: 'http://registry.npm.taobao.org/ajv-keywords/download/ajv-keywords-2.1.1.tgz' 98 | /ajv/5.5.2: 99 | dependencies: 100 | co: 4.6.0 101 | fast-deep-equal: 1.0.0 102 | fast-json-stable-stringify: 2.0.0 103 | json-schema-traverse: 0.3.1 104 | dev: true 105 | resolution: 106 | integrity: sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= 107 | tarball: 'http://registry.npm.taobao.org/ajv/download/ajv-5.5.2.tgz' 108 | /ansi-escapes/3.0.0: 109 | dev: true 110 | engines: 111 | node: '>=4' 112 | resolution: 113 | integrity: sha1-7D6LTp+AZPwCw6ybZfHCdb2o75I= 114 | tarball: 'http://registry.npm.taobao.org/ansi-escapes/download/ansi-escapes-3.0.0.tgz' 115 | /ansi-regex/2.1.1: 116 | engines: 117 | node: '>=0.10.0' 118 | resolution: 119 | integrity: sha1-w7M6te42DYbg5ijwRorn7yfWVN8= 120 | tarball: 'http://registry.npm.taobao.org/ansi-regex/download/ansi-regex-2.1.1.tgz' 121 | /ansi-regex/3.0.0: 122 | dev: true 123 | engines: 124 | node: '>=4' 125 | resolution: 126 | integrity: sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= 127 | tarball: 'http://registry.npm.taobao.org/ansi-regex/download/ansi-regex-3.0.0.tgz' 128 | /ansi-styles/2.2.1: 129 | dev: true 130 | engines: 131 | node: '>=0.10.0' 132 | resolution: 133 | integrity: sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= 134 | tarball: 'http://registry.npm.taobao.org/ansi-styles/download/ansi-styles-2.2.1.tgz' 135 | /ansi-styles/3.2.0: 136 | dependencies: 137 | color-convert: 1.9.1 138 | engines: 139 | node: '>=4' 140 | resolution: 141 | integrity: sha1-wVm41b4PnlpvNG2rlPFs4CIWG4g= 142 | tarball: 'http://registry.npm.taobao.org/ansi-styles/download/ansi-styles-3.2.0.tgz' 143 | /ansicolors/0.2.1: 144 | dev: false 145 | resolution: 146 | integrity: sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8= 147 | tarball: 'http://registry.npm.taobao.org/ansicolors/download/ansicolors-0.2.1.tgz' 148 | /any-promise/1.3.0: 149 | resolution: 150 | integrity: sha1-q8av7tzqUugJzcA3au0845Y10X8= 151 | tarball: 'http://registry.npm.taobao.org/any-promise/download/any-promise-1.3.0.tgz' 152 | /argparse/1.0.10: 153 | dependencies: 154 | sprintf-js: 1.0.3 155 | dev: true 156 | resolution: 157 | integrity: sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE= 158 | tarball: 'http://registry.npm.taobao.org/argparse/download/argparse-1.0.10.tgz' 159 | /array-union/1.0.2: 160 | dependencies: 161 | array-uniq: 1.0.3 162 | dev: true 163 | engines: 164 | node: '>=0.10.0' 165 | resolution: 166 | integrity: sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= 167 | tarball: 'http://registry.npm.taobao.org/array-union/download/array-union-1.0.2.tgz' 168 | /array-uniq/1.0.3: 169 | dev: true 170 | engines: 171 | node: '>=0.10.0' 172 | resolution: 173 | integrity: sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= 174 | tarball: 'http://registry.npm.taobao.org/array-uniq/download/array-uniq-1.0.3.tgz' 175 | /arrify/1.0.1: 176 | dev: true 177 | engines: 178 | node: '>=0.10.0' 179 | resolution: 180 | integrity: sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= 181 | tarball: 'http://registry.npm.taobao.org/arrify/download/arrify-1.0.1.tgz' 182 | /assertion-error/1.1.0: 183 | dev: true 184 | resolution: 185 | integrity: sha1-5gtrDo8wG9l+U3UhW9pAbIURjAs= 186 | tarball: 'http://registry.npm.taobao.org/assertion-error/download/assertion-error-1.1.0.tgz' 187 | /async/1.0.0: 188 | dev: false 189 | resolution: 190 | integrity: sha1-+PwEyjoTeErenhZBr5hXjPvWR6k= 191 | tarball: 'http://registry.npm.taobao.org/async/download/async-1.0.0.tgz' 192 | /async/1.5.2: 193 | dev: false 194 | resolution: 195 | integrity: sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= 196 | tarball: 'http://registry.npm.taobao.org/async/download/async-1.5.2.tgz' 197 | /async/2.6.0: 198 | dependencies: 199 | lodash: 4.17.5 200 | dev: false 201 | resolution: 202 | integrity: sha1-YaKau2/MAm/qd+VtHG7FOnlZUfQ= 203 | tarball: 'http://registry.npm.taobao.org/async/download/async-2.6.0.tgz' 204 | /axios/0.17.1: 205 | dependencies: 206 | follow-redirects: 1.4.1 207 | is-buffer: 1.1.6 208 | dev: false 209 | resolution: 210 | integrity: sha1-LY4+XQvb1zJ/kbyBT1xXZg+Bgk0= 211 | tarball: 'http://registry.npm.taobao.org/axios/download/axios-0.17.1.tgz' 212 | /babel-code-frame/6.26.0: 213 | dependencies: 214 | chalk: 1.1.3 215 | esutils: 2.0.2 216 | js-tokens: 3.0.2 217 | dev: true 218 | resolution: 219 | integrity: sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= 220 | tarball: 'http://registry.npm.taobao.org/babel-code-frame/download/babel-code-frame-6.26.0.tgz' 221 | /balanced-match/1.0.0: 222 | dev: true 223 | resolution: 224 | integrity: sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 225 | tarball: 'http://registry.npm.taobao.org/balanced-match/download/balanced-match-1.0.0.tgz' 226 | /big-integer/1.6.28: 227 | dev: false 228 | engines: 229 | node: '>=0.6' 230 | resolution: 231 | integrity: sha1-jO8P2jzN6HWcLGbvz6zDWuplgoM= 232 | tarball: 'http://registry.npm.taobao.org/big-integer/download/big-integer-1.6.28.tgz' 233 | /bluebird/3.5.1: 234 | dev: false 235 | resolution: 236 | integrity: sha1-2VUfnemPH82h5oPRfukaBgLuLrk= 237 | tarball: 'http://registry.npm.taobao.org/bluebird/download/bluebird-3.5.1.tgz' 238 | /brace-expansion/1.1.11: 239 | dependencies: 240 | balanced-match: 1.0.0 241 | concat-map: 0.0.1 242 | dev: true 243 | resolution: 244 | integrity: sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0= 245 | tarball: 'http://registry.npm.taobao.org/brace-expansion/download/brace-expansion-1.1.11.tgz' 246 | /browser-stdout/1.3.0: 247 | dev: true 248 | resolution: 249 | integrity: sha1-81HTKWnTL6XXpVZxVCY9korjvR8= 250 | tarball: 'http://registry.npm.taobao.org/browser-stdout/download/browser-stdout-1.3.0.tgz' 251 | /builtin-modules/1.1.1: 252 | dev: true 253 | engines: 254 | node: '>=0.10.0' 255 | resolution: 256 | integrity: sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8= 257 | tarball: 'http://registry.npm.taobao.org/builtin-modules/download/builtin-modules-1.1.1.tgz' 258 | /bytes/2.5.0: 259 | dev: false 260 | engines: 261 | node: '>= 0.6' 262 | resolution: 263 | integrity: sha1-TJQj6i0lLCcMQbK97+/5u2tiwGo= 264 | tarball: 'http://registry.npm.taobao.org/bytes/download/bytes-2.5.0.tgz' 265 | /bytes/3.0.0: 266 | dev: false 267 | engines: 268 | node: '>= 0.8' 269 | resolution: 270 | integrity: sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= 271 | tarball: 'http://registry.npm.taobao.org/bytes/download/bytes-3.0.0.tgz' 272 | /caller-path/0.1.0: 273 | dependencies: 274 | callsites: 0.2.0 275 | dev: true 276 | engines: 277 | node: '>=0.10.0' 278 | resolution: 279 | integrity: sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8= 280 | tarball: 'http://registry.npm.taobao.org/caller-path/download/caller-path-0.1.0.tgz' 281 | /callsites/0.2.0: 282 | dev: true 283 | engines: 284 | node: '>=0.10.0' 285 | resolution: 286 | integrity: sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo= 287 | tarball: 'http://registry.npm.taobao.org/callsites/download/callsites-0.2.0.tgz' 288 | /camelcase/2.1.1: 289 | dev: false 290 | engines: 291 | node: '>=0.10.0' 292 | resolution: 293 | integrity: sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= 294 | tarball: 'http://registry.npm.taobao.org/camelcase/download/camelcase-2.1.1.tgz' 295 | /camelize/1.0.0: 296 | dev: false 297 | resolution: 298 | integrity: sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= 299 | tarball: 'http://registry.npm.taobao.org/camelize/download/camelize-1.0.0.tgz' 300 | /cardinal/1.0.0: 301 | dependencies: 302 | ansicolors: 0.2.1 303 | redeyed: 1.0.1 304 | dev: false 305 | resolution: 306 | integrity: sha1-UOIcGwqjdyn5N33vGWtanOyTLuk= 307 | tarball: 'http://registry.npm.taobao.org/cardinal/download/cardinal-1.0.0.tgz' 308 | /chai/4.1.2: 309 | dependencies: 310 | assertion-error: 1.1.0 311 | check-error: 1.0.2 312 | deep-eql: 3.0.1 313 | get-func-name: 2.0.0 314 | pathval: 1.1.0 315 | type-detect: 4.0.8 316 | dev: true 317 | engines: 318 | node: '>=4' 319 | resolution: 320 | integrity: sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw= 321 | tarball: 'http://registry.npm.taobao.org/chai/download/chai-4.1.2.tgz' 322 | /chalk/1.1.3: 323 | dependencies: 324 | ansi-styles: 2.2.1 325 | escape-string-regexp: 1.0.5 326 | has-ansi: 2.0.0 327 | strip-ansi: 3.0.1 328 | supports-color: 2.0.0 329 | dev: true 330 | engines: 331 | node: '>=0.10.0' 332 | resolution: 333 | integrity: sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= 334 | tarball: 'http://registry.npm.taobao.org/chalk/download/chalk-1.1.3.tgz' 335 | /chalk/2.3.1: 336 | dependencies: 337 | ansi-styles: 3.2.0 338 | escape-string-regexp: 1.0.5 339 | supports-color: 5.2.0 340 | engines: 341 | node: '>=4' 342 | resolution: 343 | integrity: sha1-Uj/iZ4rsewToBBkJKS/osXBZt5Y= 344 | tarball: 'http://registry.npm.taobao.org/chalk/download/chalk-2.3.1.tgz' 345 | /chardet/0.4.2: 346 | dev: true 347 | resolution: 348 | integrity: sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I= 349 | tarball: 'http://registry.npm.taobao.org/chardet/download/chardet-0.4.2.tgz' 350 | /check-error/1.0.2: 351 | dev: true 352 | resolution: 353 | integrity: sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= 354 | tarball: 'http://registry.npm.taobao.org/check-error/download/check-error-1.0.2.tgz' 355 | /circular-json/0.3.3: 356 | dev: true 357 | resolution: 358 | integrity: sha1-gVyZ6oT2gJUp0vRXkb34JxE1LWY= 359 | tarball: 'http://registry.npm.taobao.org/circular-json/download/circular-json-0.3.3.tgz' 360 | /cli-cursor/2.1.0: 361 | dependencies: 362 | restore-cursor: 2.0.0 363 | dev: true 364 | engines: 365 | node: '>=4' 366 | resolution: 367 | integrity: sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= 368 | tarball: 'http://registry.npm.taobao.org/cli-cursor/download/cli-cursor-2.1.0.tgz' 369 | /cli-width/2.2.0: 370 | dev: true 371 | resolution: 372 | integrity: sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= 373 | tarball: 'http://registry.npm.taobao.org/cli-width/download/cli-width-2.2.0.tgz' 374 | /cliui/3.2.0: 375 | dependencies: 376 | string-width: 1.0.2 377 | strip-ansi: 3.0.1 378 | wrap-ansi: 2.1.0 379 | dev: false 380 | resolution: 381 | integrity: sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= 382 | tarball: 'http://registry.npm.taobao.org/cliui/download/cliui-3.2.0.tgz' 383 | /cls-bluebird/2.1.0: 384 | dependencies: 385 | is-bluebird: 1.0.2 386 | shimmer: 1.2.0 387 | dev: false 388 | resolution: 389 | integrity: sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4= 390 | tarball: 'http://registry.npm.taobao.org/cls-bluebird/download/cls-bluebird-2.1.0.tgz' 391 | /co-body/5.1.1: 392 | dependencies: 393 | inflation: 2.0.0 394 | qs: 6.5.1 395 | raw-body: 2.3.2 396 | type-is: 1.6.16 397 | dev: false 398 | resolution: 399 | integrity: sha1-2XeB0eM0S6SoIP0YBr3fg0FQUjY= 400 | tarball: 'http://registry.npm.taobao.org/co-body/download/co-body-5.1.1.tgz' 401 | /co/4.6.0: 402 | engines: 403 | iojs: '>= 1.0.0' 404 | node: '>= 0.12.0' 405 | resolution: 406 | integrity: sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= 407 | tarball: 'http://registry.npm.taobao.org/co/download/co-4.6.0.tgz' 408 | /code-point-at/1.1.0: 409 | dev: false 410 | engines: 411 | node: '>=0.10.0' 412 | resolution: 413 | integrity: sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= 414 | tarball: 'http://registry.npm.taobao.org/code-point-at/download/code-point-at-1.1.0.tgz' 415 | /color-convert/1.9.1: 416 | dependencies: 417 | color-name: 1.1.3 418 | resolution: 419 | integrity: sha1-wSYRB66y8pTr/+ye2eytUppgl+0= 420 | tarball: 'http://registry.npm.taobao.org/color-convert/download/color-convert-1.9.1.tgz' 421 | /color-name/1.1.3: 422 | resolution: 423 | integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 424 | tarball: 'http://registry.npm.taobao.org/color-name/download/color-name-1.1.3.tgz' 425 | /colors/1.0.3: 426 | dev: false 427 | engines: 428 | node: '>=0.1.90' 429 | resolution: 430 | integrity: sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= 431 | tarball: 'http://registry.npm.taobao.org/colors/download/colors-1.0.3.tgz' 432 | /colors/1.1.2: 433 | dev: false 434 | engines: 435 | node: '>=0.1.90' 436 | resolution: 437 | integrity: sha1-FopHAXVran9RoSzgyXv6KMCE7WM= 438 | tarball: 'http://registry.npm.taobao.org/colors/download/colors-1.1.2.tgz' 439 | /commander/2.11.0: 440 | dev: true 441 | resolution: 442 | integrity: sha1-FXFS/R56bI2YpbcVzzdt+SgARWM= 443 | tarball: 'http://registry.npm.taobao.org/commander/download/commander-2.11.0.tgz' 444 | /compressible/2.0.13: 445 | dependencies: 446 | mime-db: 1.33.0 447 | dev: false 448 | engines: 449 | node: '>= 0.6' 450 | resolution: 451 | integrity: sha1-DRAgq5JLL9tNYnmHXH1tq6a6p6k= 452 | tarball: 'http://registry.npm.taobao.org/compressible/download/compressible-2.0.13.tgz' 453 | /concat-map/0.0.1: 454 | dev: true 455 | resolution: 456 | integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 457 | tarball: 'http://registry.npm.taobao.org/concat-map/download/concat-map-0.0.1.tgz' 458 | /concat-stream/1.6.0: 459 | dependencies: 460 | inherits: 2.0.3 461 | readable-stream: 2.3.4 462 | typedarray: 0.0.6 463 | dev: true 464 | engines: 465 | '0': node >= 0.8 466 | resolution: 467 | integrity: sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc= 468 | tarball: 'http://registry.npm.taobao.org/concat-stream/download/concat-stream-1.6.0.tgz' 469 | /contains-path/0.1.0: 470 | dev: true 471 | engines: 472 | node: '>=0.10.0' 473 | resolution: 474 | integrity: sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= 475 | tarball: 'http://registry.npm.taobao.org/contains-path/download/contains-path-0.1.0.tgz' 476 | /content-disposition/0.5.2: 477 | engines: 478 | node: '>= 0.6' 479 | resolution: 480 | integrity: sha1-DPaLud318r55YcOoUXjLhdunjLQ= 481 | tarball: 'http://registry.npm.taobao.org/content-disposition/download/content-disposition-0.5.2.tgz' 482 | /content-security-policy-builder/2.0.0: 483 | dev: false 484 | resolution: 485 | integrity: sha1-h0mh1UL8voIjcoHqn3Fs5os5TdI= 486 | tarball: 'http://registry.npm.taobao.org/content-security-policy-builder/download/content-security-policy-builder-2.0.0.tgz' 487 | /content-type/1.0.4: 488 | engines: 489 | node: '>= 0.6' 490 | resolution: 491 | integrity: sha1-4TjMdeBAxyexlm/l5fjJruJW/js= 492 | tarball: 'http://registry.npm.taobao.org/content-type/download/content-type-1.0.4.tgz' 493 | /cookies/0.7.1: 494 | dependencies: 495 | depd: 1.1.2 496 | keygrip: 1.0.2 497 | engines: 498 | node: '>= 0.8' 499 | resolution: 500 | integrity: sha1-fIphX1SBxhq58WyDNzG8uPZjuZs= 501 | tarball: 'http://registry.npm.taobao.org/cookies/download/cookies-0.7.1.tgz' 502 | /copy-to/2.0.1: 503 | dev: false 504 | resolution: 505 | integrity: sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU= 506 | tarball: 'http://registry.npm.taobao.org/copy-to/download/copy-to-2.0.1.tgz' 507 | /core-util-is/1.0.2: 508 | resolution: 509 | integrity: sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= 510 | tarball: 'http://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz' 511 | /cron/1.3.0: 512 | dependencies: 513 | moment-timezone: 0.5.14 514 | dev: false 515 | resolution: 516 | integrity: sha1-fkWZaOr5ThpEW+eWzkAhZsI0ZZ0= 517 | tarball: 'http://registry.npm.taobao.org/cron/download/cron-1.3.0.tgz' 518 | /cross-spawn/5.1.0: 519 | dependencies: 520 | lru-cache: 4.1.1 521 | shebang-command: 1.2.0 522 | which: 1.3.0 523 | dev: true 524 | resolution: 525 | integrity: sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= 526 | tarball: 'http://registry.npm.taobao.org/cross-spawn/download/cross-spawn-5.1.0.tgz' 527 | /cycle/1.0.3: 528 | dev: false 529 | engines: 530 | node: '>=0.4.0' 531 | resolution: 532 | integrity: sha1-IegLK+hYD5i0aPN5QwZisEbDStI= 533 | tarball: 'http://registry.npm.taobao.org/cycle/download/cycle-1.0.3.tgz' 534 | /dasherize/2.0.0: 535 | dev: false 536 | resolution: 537 | integrity: sha1-bYCcnNDPe7iVLYD8hPoT1H3bEwg= 538 | tarball: 'http://registry.npm.taobao.org/dasherize/download/dasherize-2.0.0.tgz' 539 | /debug/2.6.9: 540 | dependencies: 541 | ms: 2.0.0 542 | resolution: 543 | integrity: sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8= 544 | tarball: 'http://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz' 545 | /debug/3.1.0: 546 | dependencies: 547 | ms: 2.0.0 548 | resolution: 549 | integrity: sha1-W7WgZyYotkFJVmuhaBnmFRjGcmE= 550 | tarball: 'http://registry.npm.taobao.org/debug/download/debug-3.1.0.tgz' 551 | /decamelize/1.2.0: 552 | dev: false 553 | engines: 554 | node: '>=0.10.0' 555 | resolution: 556 | integrity: sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= 557 | tarball: 'http://registry.npm.taobao.org/decamelize/download/decamelize-1.2.0.tgz' 558 | /deep-eql/3.0.1: 559 | dependencies: 560 | type-detect: 4.0.8 561 | dev: true 562 | engines: 563 | node: '>=0.12' 564 | resolution: 565 | integrity: sha1-38lARACtHI/gI+faHfHBR8S0RN8= 566 | tarball: 'http://registry.npm.taobao.org/deep-eql/download/deep-eql-3.0.1.tgz' 567 | /deep-equal/1.0.1: 568 | resolution: 569 | integrity: sha1-9dJgKStmDghO/0zbyfCK0yR0SLU= 570 | tarball: 'http://registry.npm.taobao.org/deep-equal/download/deep-equal-1.0.1.tgz' 571 | /deep-is/0.1.3: 572 | dev: true 573 | resolution: 574 | integrity: sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= 575 | tarball: 'http://registry.npm.taobao.org/deep-is/download/deep-is-0.1.3.tgz' 576 | /del/2.2.2: 577 | dependencies: 578 | globby: 5.0.0 579 | is-path-cwd: 1.0.0 580 | is-path-in-cwd: 1.0.0 581 | object-assign: 4.1.1 582 | pify: 2.3.0 583 | pinkie-promise: 2.0.1 584 | rimraf: 2.6.2 585 | dev: true 586 | engines: 587 | node: '>=0.10.0' 588 | resolution: 589 | integrity: sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag= 590 | tarball: 'http://registry.npm.taobao.org/del/download/del-2.2.2.tgz' 591 | /delegates/1.0.0: 592 | resolution: 593 | integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= 594 | tarball: 'http://registry.npm.taobao.org/delegates/download/delegates-1.0.0.tgz' 595 | /denque/1.2.3: 596 | dev: false 597 | engines: 598 | node: '>=0.10' 599 | resolution: 600 | integrity: sha1-mMUMjdjN+uMYzFhZzI7j2g+bDMI= 601 | tarball: 'http://registry.npm.taobao.org/denque/download/denque-1.2.3.tgz' 602 | /depd/1.1.1: 603 | engines: 604 | node: '>= 0.6' 605 | resolution: 606 | integrity: sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k= 607 | tarball: 'http://registry.npm.taobao.org/depd/download/depd-1.1.1.tgz' 608 | /depd/1.1.2: 609 | engines: 610 | node: '>= 0.6' 611 | resolution: 612 | integrity: sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= 613 | tarball: 'http://registry.npm.taobao.org/depd/download/depd-1.1.2.tgz' 614 | /destroy/1.0.4: 615 | resolution: 616 | integrity: sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= 617 | tarball: 'http://registry.npm.taobao.org/destroy/download/destroy-1.0.4.tgz' 618 | /diff/3.3.1: 619 | dev: true 620 | engines: 621 | node: '>=0.3.1' 622 | resolution: 623 | integrity: sha1-qoVnpu7QPFMfyJ0/cRzQ5SWd7HU= 624 | tarball: 'http://registry.npm.taobao.org/diff/download/diff-3.3.1.tgz' 625 | /dns-prefetch-control/0.1.0: 626 | dev: false 627 | resolution: 628 | integrity: sha1-YN20V3dOF48flBXwyrsOhbCzALI= 629 | tarball: 'http://registry.npm.taobao.org/dns-prefetch-control/download/dns-prefetch-control-0.1.0.tgz' 630 | /doctrine/1.5.0: 631 | dependencies: 632 | esutils: 2.0.2 633 | isarray: 1.0.0 634 | dev: true 635 | engines: 636 | node: '>=0.10.0' 637 | resolution: 638 | integrity: sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= 639 | tarball: 'http://registry.npm.taobao.org/doctrine/download/doctrine-1.5.0.tgz' 640 | /doctrine/2.1.0: 641 | dependencies: 642 | esutils: 2.0.2 643 | dev: true 644 | engines: 645 | node: '>=0.10.0' 646 | resolution: 647 | integrity: sha1-XNAfwQFiG0LEzX9dGmYkNxbT850= 648 | tarball: 'http://registry.npm.taobao.org/doctrine/download/doctrine-2.1.0.tgz' 649 | /dont-sniff-mimetype/1.0.0: 650 | dev: false 651 | resolution: 652 | integrity: sha1-WTKJDcn04vGeXrAqIAJuXl78j1g= 653 | tarball: 'http://registry.npm.taobao.org/dont-sniff-mimetype/download/dont-sniff-mimetype-1.0.0.tgz' 654 | /dottie/2.0.0: 655 | dev: false 656 | resolution: 657 | integrity: sha1-2hkZgci41xPKARXViYzzl8Lw3dA= 658 | tarball: 'http://registry.npm.taobao.org/dottie/download/dottie-2.0.0.tgz' 659 | /double-ended-queue/2.1.0-0: 660 | dev: false 661 | resolution: 662 | integrity: sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw= 663 | tarball: 'http://registry.npm.taobao.org/double-ended-queue/download/double-ended-queue-2.1.0-0.tgz' 664 | /ee-first/1.1.1: 665 | resolution: 666 | integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= 667 | tarball: 'http://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz' 668 | /error-ex/1.3.1: 669 | dependencies: 670 | is-arrayish: 0.2.1 671 | dev: true 672 | resolution: 673 | integrity: sha1-+FWobOYa3E6GIcPNoh56dhLDqNw= 674 | tarball: 'http://registry.npm.taobao.org/error-ex/download/error-ex-1.3.1.tgz' 675 | /error-inject/1.0.0: 676 | resolution: 677 | integrity: sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc= 678 | tarball: 'http://registry.npm.taobao.org/error-inject/download/error-inject-1.0.0.tgz' 679 | /escape-html/1.0.3: 680 | resolution: 681 | integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= 682 | tarball: 'http://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz' 683 | /escape-string-regexp/1.0.5: 684 | engines: 685 | node: '>=0.8.0' 686 | resolution: 687 | integrity: sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 688 | tarball: 'http://registry.npm.taobao.org/escape-string-regexp/download/escape-string-regexp-1.0.5.tgz' 689 | /eslint-config-standard/11.0.0: 690 | dev: true 691 | peerDependencies: 692 | eslint: '>=4.18.0' 693 | eslint-plugin-import: '>=2.8.0' 694 | eslint-plugin-node: '>=5.2.1' 695 | eslint-plugin-promise: '>=3.6.0' 696 | eslint-plugin-standard: '>=3.0.1' 697 | resolution: 698 | integrity: sha1-h+4NPJ2VOC3HYZWMuyPanuox4Lo= 699 | tarball: 'http://registry.npm.taobao.org/eslint-config-standard/download/eslint-config-standard-11.0.0.tgz' 700 | /eslint-import-resolver-node/0.3.2: 701 | dependencies: 702 | debug: 2.6.9 703 | resolve: 1.5.0 704 | dev: true 705 | resolution: 706 | integrity: sha1-WPFfuDm40FdsqYBBNHaqskcttmo= 707 | tarball: 'http://registry.npm.taobao.org/eslint-import-resolver-node/download/eslint-import-resolver-node-0.3.2.tgz' 708 | /eslint-module-utils/2.1.1: 709 | dependencies: 710 | debug: 2.6.9 711 | pkg-dir: 1.0.0 712 | dev: true 713 | engines: 714 | node: '>=4' 715 | resolution: 716 | integrity: sha1-q67IJBd2E7ipWymWOeG2+s9HNEk= 717 | tarball: 'http://registry.npm.taobao.org/eslint-module-utils/download/eslint-module-utils-2.1.1.tgz' 718 | /eslint-plugin-import/2.8.0: 719 | dependencies: 720 | builtin-modules: 1.1.1 721 | contains-path: 0.1.0 722 | debug: 2.6.9 723 | doctrine: 1.5.0 724 | eslint-import-resolver-node: 0.3.2 725 | eslint-module-utils: 2.1.1 726 | has: 1.0.1 727 | lodash.cond: 4.5.2 728 | minimatch: 3.0.4 729 | read-pkg-up: 2.0.0 730 | dev: true 731 | engines: 732 | node: '>=4' 733 | peerDependencies: 734 | eslint: 2.x - 4.x 735 | resolution: 736 | integrity: sha1-+htu8x/LPFAcCYWcG4bx/FuYaJQ= 737 | tarball: 'http://registry.npm.taobao.org/eslint-plugin-import/download/eslint-plugin-import-2.8.0.tgz' 738 | /eslint-plugin-node/5.2.1: 739 | dependencies: 740 | ignore: 3.3.7 741 | minimatch: 3.0.4 742 | resolve: 1.5.0 743 | semver: 5.3.0 744 | dev: true 745 | engines: 746 | node: '>=4' 747 | peerDependencies: 748 | eslint: '>=3.1.0' 749 | resolution: 750 | integrity: sha1-gN8yU8TXkBBF7If6ZgooTjK9yik= 751 | tarball: 'http://registry.npm.taobao.org/eslint-plugin-node/download/eslint-plugin-node-5.2.1.tgz' 752 | /eslint-plugin-promise/3.6.0: 753 | dev: true 754 | engines: 755 | node: '>=4' 756 | resolution: 757 | integrity: sha1-VLdljI9FSBPcKocK/4FS7ElpunU= 758 | tarball: 'http://registry.npm.taobao.org/eslint-plugin-promise/download/eslint-plugin-promise-3.6.0.tgz' 759 | /eslint-plugin-standard/3.0.1: 760 | dev: true 761 | peerDependencies: 762 | eslint: '>=3.19.0' 763 | resolution: 764 | integrity: sha1-NNDJFbRe3G8BA5PH7vOCOwhWXPI= 765 | tarball: 'http://registry.npm.taobao.org/eslint-plugin-standard/download/eslint-plugin-standard-3.0.1.tgz' 766 | /eslint-scope/3.7.1: 767 | dependencies: 768 | esrecurse: 4.2.0 769 | estraverse: 4.2.0 770 | dev: true 771 | engines: 772 | node: '>=4.0.0' 773 | resolution: 774 | integrity: sha1-PWPD7f2gLgbgGkUq2IyqzHzctug= 775 | tarball: 'http://registry.npm.taobao.org/eslint-scope/download/eslint-scope-3.7.1.tgz' 776 | /eslint-visitor-keys/1.0.0: 777 | dev: true 778 | engines: 779 | node: '>=4' 780 | resolution: 781 | integrity: sha1-PzGA+y4pEBdxastMnW1bXDSmqB0= 782 | tarball: 'http://registry.npm.taobao.org/eslint-visitor-keys/download/eslint-visitor-keys-1.0.0.tgz' 783 | /eslint/4.18.0: 784 | dependencies: 785 | ajv: 5.5.2 786 | babel-code-frame: 6.26.0 787 | chalk: 2.3.1 788 | concat-stream: 1.6.0 789 | cross-spawn: 5.1.0 790 | debug: 3.1.0 791 | doctrine: 2.1.0 792 | eslint-scope: 3.7.1 793 | eslint-visitor-keys: 1.0.0 794 | espree: 3.5.3 795 | esquery: 1.0.0 796 | esutils: 2.0.2 797 | file-entry-cache: 2.0.0 798 | functional-red-black-tree: 1.0.1 799 | glob: 7.1.2 800 | globals: 11.3.0 801 | ignore: 3.3.7 802 | imurmurhash: 0.1.4 803 | inquirer: 3.3.0 804 | is-resolvable: 1.1.0 805 | js-yaml: 3.10.0 806 | json-stable-stringify-without-jsonify: 1.0.1 807 | levn: 0.3.0 808 | lodash: 4.17.5 809 | minimatch: 3.0.4 810 | mkdirp: 0.5.1 811 | natural-compare: 1.4.0 812 | optionator: 0.8.2 813 | path-is-inside: 1.0.2 814 | pluralize: 7.0.0 815 | progress: 2.0.0 816 | require-uncached: 1.0.3 817 | semver: 5.5.0 818 | strip-ansi: 4.0.0 819 | strip-json-comments: 2.0.1 820 | table: 4.0.2 821 | text-table: 0.2.0 822 | dev: true 823 | engines: 824 | node: '>=4' 825 | resolution: 826 | integrity: sha1-69C6eVr23FmqXO4Xk4Fgr1lQ4FE= 827 | tarball: 'http://registry.npm.taobao.org/eslint/download/eslint-4.18.0.tgz' 828 | /espree/3.5.3: 829 | dependencies: 830 | acorn: 5.4.1 831 | acorn-jsx: 3.0.1 832 | dev: true 833 | engines: 834 | node: '>=0.10.0' 835 | resolution: 836 | integrity: sha1-kx4K9k5/u+0msFCinarR/GR5n6Y= 837 | tarball: 'http://registry.npm.taobao.org/espree/download/espree-3.5.3.tgz' 838 | /esprima/3.0.0: 839 | dev: false 840 | engines: 841 | node: '>=0.10.0' 842 | resolution: 843 | integrity: sha1-U88kes2ncxPlUcOqLnM0LT+099k= 844 | tarball: 'http://registry.npm.taobao.org/esprima/download/esprima-3.0.0.tgz' 845 | /esprima/4.0.0: 846 | dev: true 847 | engines: 848 | node: '>=4' 849 | resolution: 850 | integrity: sha1-RJnt3NERDgshi6zy+n9/WfVcqAQ= 851 | tarball: 'http://registry.npm.taobao.org/esprima/download/esprima-4.0.0.tgz' 852 | /esquery/1.0.0: 853 | dependencies: 854 | estraverse: 4.2.0 855 | dev: true 856 | engines: 857 | node: '>=0.6' 858 | resolution: 859 | integrity: sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo= 860 | tarball: 'http://registry.npm.taobao.org/esquery/download/esquery-1.0.0.tgz' 861 | /esrecurse/4.2.0: 862 | dependencies: 863 | estraverse: 4.2.0 864 | object-assign: 4.1.1 865 | dev: true 866 | engines: 867 | node: '>=0.10.0' 868 | resolution: 869 | integrity: sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM= 870 | tarball: 'http://registry.npm.taobao.org/esrecurse/download/esrecurse-4.2.0.tgz' 871 | /estraverse/4.2.0: 872 | dev: true 873 | engines: 874 | node: '>=0.10.0' 875 | resolution: 876 | integrity: sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= 877 | tarball: 'http://registry.npm.taobao.org/estraverse/download/estraverse-4.2.0.tgz' 878 | /esutils/2.0.2: 879 | dev: true 880 | engines: 881 | node: '>=0.10.0' 882 | resolution: 883 | integrity: sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= 884 | tarball: 'http://registry.npm.taobao.org/esutils/download/esutils-2.0.2.tgz' 885 | /expect-ct/0.1.0: 886 | dev: false 887 | resolution: 888 | integrity: sha1-UnNWeN4YUwiQ2Ne5XwrGNkCVgJQ= 889 | tarball: 'http://registry.npm.taobao.org/expect-ct/download/expect-ct-0.1.0.tgz' 890 | /external-editor/2.1.0: 891 | dependencies: 892 | chardet: 0.4.2 893 | iconv-lite: 0.4.19 894 | tmp: 0.0.33 895 | dev: true 896 | engines: 897 | node: '>=0.12' 898 | resolution: 899 | integrity: sha1-PQJqIbf5W1cmOH1CAKwWDTcsO0g= 900 | tarball: 'http://registry.npm.taobao.org/external-editor/download/external-editor-2.1.0.tgz' 901 | /eyes/0.1.8: 902 | dev: false 903 | engines: 904 | node: '> 0.1.90' 905 | resolution: 906 | integrity: sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A= 907 | tarball: 'http://registry.npm.taobao.org/eyes/download/eyes-0.1.8.tgz' 908 | /fast-deep-equal/1.0.0: 909 | dev: true 910 | resolution: 911 | integrity: sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8= 912 | tarball: 'http://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-1.0.0.tgz' 913 | /fast-json-stable-stringify/2.0.0: 914 | dev: true 915 | resolution: 916 | integrity: sha1-1RQsDK7msRifh9OnYREGT4bIu/I= 917 | tarball: 'http://registry.npm.taobao.org/fast-json-stable-stringify/download/fast-json-stable-stringify-2.0.0.tgz' 918 | /fast-levenshtein/2.0.6: 919 | dev: true 920 | resolution: 921 | integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= 922 | tarball: 'http://registry.npm.taobao.org/fast-levenshtein/download/fast-levenshtein-2.0.6.tgz' 923 | /figures/2.0.0: 924 | dependencies: 925 | escape-string-regexp: 1.0.5 926 | dev: true 927 | engines: 928 | node: '>=4' 929 | resolution: 930 | integrity: sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= 931 | tarball: 'http://registry.npm.taobao.org/figures/download/figures-2.0.0.tgz' 932 | /file-entry-cache/2.0.0: 933 | dependencies: 934 | flat-cache: 1.3.0 935 | object-assign: 4.1.1 936 | dev: true 937 | engines: 938 | node: '>=0.10.0' 939 | resolution: 940 | integrity: sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E= 941 | tarball: 'http://registry.npm.taobao.org/file-entry-cache/download/file-entry-cache-2.0.0.tgz' 942 | /find-up/1.1.2: 943 | dependencies: 944 | path-exists: 2.1.0 945 | pinkie-promise: 2.0.1 946 | dev: true 947 | engines: 948 | node: '>=0.10.0' 949 | resolution: 950 | integrity: sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8= 951 | tarball: 'http://registry.npm.taobao.org/find-up/download/find-up-1.1.2.tgz' 952 | /find-up/2.1.0: 953 | dependencies: 954 | locate-path: 2.0.0 955 | dev: true 956 | engines: 957 | node: '>=4' 958 | resolution: 959 | integrity: sha1-RdG35QbHF93UgndaK3eSCjwMV6c= 960 | tarball: 'http://registry.npm.taobao.org/find-up/download/find-up-2.1.0.tgz' 961 | /flat-cache/1.3.0: 962 | dependencies: 963 | circular-json: 0.3.3 964 | del: 2.2.2 965 | graceful-fs: 4.1.11 966 | write: 0.2.1 967 | dev: true 968 | engines: 969 | node: '>=0.10.0' 970 | resolution: 971 | integrity: sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE= 972 | tarball: 'http://registry.npm.taobao.org/flat-cache/download/flat-cache-1.3.0.tgz' 973 | /follow-redirects/1.4.1: 974 | dependencies: 975 | debug: 3.1.0 976 | dev: false 977 | engines: 978 | node: '>=4.0' 979 | resolution: 980 | integrity: sha1-2BIPRRgZD1Wqxlu2/HuF/NZm1qo= 981 | tarball: 'http://registry.npm.taobao.org/follow-redirects/download/follow-redirects-1.4.1.tgz' 982 | /frameguard/3.0.0: 983 | dev: false 984 | resolution: 985 | integrity: sha1-e8rUae57lukdEs6zlZx4I1qScuk= 986 | tarball: 'http://registry.npm.taobao.org/frameguard/download/frameguard-3.0.0.tgz' 987 | /fresh/0.5.2: 988 | engines: 989 | node: '>= 0.6' 990 | resolution: 991 | integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= 992 | tarball: 'http://registry.npm.taobao.org/fresh/download/fresh-0.5.2.tgz' 993 | /fs.realpath/1.0.0: 994 | dev: true 995 | resolution: 996 | integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 997 | tarball: 'http://registry.npm.taobao.org/fs.realpath/download/fs.realpath-1.0.0.tgz' 998 | /function-bind/1.1.1: 999 | dev: true 1000 | resolution: 1001 | integrity: sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0= 1002 | tarball: 'http://registry.npm.taobao.org/function-bind/download/function-bind-1.1.1.tgz' 1003 | /functional-red-black-tree/1.0.1: 1004 | dev: true 1005 | resolution: 1006 | integrity: sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= 1007 | tarball: 'http://registry.npm.taobao.org/functional-red-black-tree/download/functional-red-black-tree-1.0.1.tgz' 1008 | /generate-function/2.0.0: 1009 | dev: false 1010 | resolution: 1011 | integrity: sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ= 1012 | tarball: 'http://registry.npm.taobao.org/generate-function/download/generate-function-2.0.0.tgz' 1013 | /generic-pool/3.4.2: 1014 | dev: false 1015 | engines: 1016 | node: '>= 4' 1017 | resolution: 1018 | integrity: sha1-kv9xllINZwg5pnMICSoSqt8valk= 1019 | tarball: 'http://registry.npm.taobao.org/generic-pool/download/generic-pool-3.4.2.tgz' 1020 | /get-func-name/2.0.0: 1021 | dev: true 1022 | resolution: 1023 | integrity: sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= 1024 | tarball: 'http://registry.npm.taobao.org/get-func-name/download/get-func-name-2.0.0.tgz' 1025 | /glob/7.1.2: 1026 | dependencies: 1027 | fs.realpath: 1.0.0 1028 | inflight: 1.0.6 1029 | inherits: 2.0.3 1030 | minimatch: 3.0.4 1031 | once: 1.4.0 1032 | path-is-absolute: 1.0.1 1033 | dev: true 1034 | resolution: 1035 | integrity: sha1-wZyd+aAocC1nhhI4SmVSQExjbRU= 1036 | tarball: 'http://registry.npm.taobao.org/glob/download/glob-7.1.2.tgz' 1037 | /globals/11.3.0: 1038 | dev: true 1039 | engines: 1040 | node: '>=4' 1041 | resolution: 1042 | integrity: sha1-4E/be5eW2K2snI9kwUg3sjEzeLA= 1043 | tarball: 'http://registry.npm.taobao.org/globals/download/globals-11.3.0.tgz' 1044 | /globby/5.0.0: 1045 | dependencies: 1046 | array-union: 1.0.2 1047 | arrify: 1.0.1 1048 | glob: 7.1.2 1049 | object-assign: 4.1.1 1050 | pify: 2.3.0 1051 | pinkie-promise: 2.0.1 1052 | dev: true 1053 | engines: 1054 | node: '>=0.10.0' 1055 | resolution: 1056 | integrity: sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0= 1057 | tarball: 'http://registry.npm.taobao.org/globby/download/globby-5.0.0.tgz' 1058 | /graceful-fs/4.1.11: 1059 | dev: true 1060 | engines: 1061 | node: '>=0.4.0' 1062 | resolution: 1063 | integrity: sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= 1064 | tarball: 'http://registry.npm.taobao.org/graceful-fs/download/graceful-fs-4.1.11.tgz' 1065 | /growl/1.10.3: 1066 | dev: true 1067 | engines: 1068 | node: '>=4.x' 1069 | resolution: 1070 | integrity: sha1-GSa6kM8+3+KttJJ/WIC8IsZseQ8= 1071 | tarball: 'http://registry.npm.taobao.org/growl/download/growl-1.10.3.tgz' 1072 | /has-ansi/2.0.0: 1073 | dependencies: 1074 | ansi-regex: 2.1.1 1075 | dev: true 1076 | engines: 1077 | node: '>=0.10.0' 1078 | resolution: 1079 | integrity: sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= 1080 | tarball: 'http://registry.npm.taobao.org/has-ansi/download/has-ansi-2.0.0.tgz' 1081 | /has-flag/2.0.0: 1082 | dev: true 1083 | engines: 1084 | node: '>=0.10.0' 1085 | resolution: 1086 | integrity: sha1-6CB68cx7MNRGzHC3NLXovhj4jVE= 1087 | tarball: 'http://registry.npm.taobao.org/has-flag/download/has-flag-2.0.0.tgz' 1088 | /has-flag/3.0.0: 1089 | engines: 1090 | node: '>=4' 1091 | resolution: 1092 | integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 1093 | tarball: 'http://registry.npm.taobao.org/has-flag/download/has-flag-3.0.0.tgz' 1094 | /has/1.0.1: 1095 | dependencies: 1096 | function-bind: 1.1.1 1097 | dev: true 1098 | engines: 1099 | node: '>= 0.8.0' 1100 | resolution: 1101 | integrity: sha1-hGFzP1OLCDfJNh45qauelwTcLyg= 1102 | tarball: 'http://registry.npm.taobao.org/has/download/has-1.0.1.tgz' 1103 | /he/1.1.1: 1104 | dev: true 1105 | resolution: 1106 | integrity: sha1-k0EP0hsAlzUVH4howvJx80J+I/0= 1107 | tarball: 'http://registry.npm.taobao.org/he/download/he-1.1.1.tgz' 1108 | /helmet-csp/2.7.0: 1109 | dependencies: 1110 | camelize: 1.0.0 1111 | content-security-policy-builder: 2.0.0 1112 | dasherize: 2.0.0 1113 | lodash.reduce: 4.6.0 1114 | platform: 1.3.5 1115 | dev: false 1116 | resolution: 1117 | integrity: sha1-eTQJRhfR/re7LcQ7t9nogw93RxY= 1118 | tarball: 'http://registry.npm.taobao.org/helmet-csp/download/helmet-csp-2.7.0.tgz' 1119 | /helmet/3.11.0: 1120 | dependencies: 1121 | dns-prefetch-control: 0.1.0 1122 | dont-sniff-mimetype: 1.0.0 1123 | expect-ct: 0.1.0 1124 | frameguard: 3.0.0 1125 | helmet-csp: 2.7.0 1126 | hide-powered-by: 1.0.0 1127 | hpkp: 2.0.0 1128 | hsts: 2.1.0 1129 | ienoopen: 1.0.0 1130 | nocache: 2.0.0 1131 | referrer-policy: 1.1.0 1132 | x-xss-protection: 1.0.0 1133 | dev: false 1134 | engines: 1135 | node: '>= 0.10.0' 1136 | resolution: 1137 | integrity: sha1-XqzMwLW2HXhuKao/xWUKv3Phgk8= 1138 | tarball: 'http://registry.npm.taobao.org/helmet/download/helmet-3.11.0.tgz' 1139 | /hide-powered-by/1.0.0: 1140 | dev: false 1141 | resolution: 1142 | integrity: sha1-SoWtZYgfYoV/xwr3F0oRhNzM4ys= 1143 | tarball: 'http://registry.npm.taobao.org/hide-powered-by/download/hide-powered-by-1.0.0.tgz' 1144 | /hosted-git-info/2.5.0: 1145 | dev: true 1146 | resolution: 1147 | integrity: sha1-bWDjSzq7yDEwYsO3mO+NkBoHrzw= 1148 | tarball: 'http://registry.npm.taobao.org/hosted-git-info/download/hosted-git-info-2.5.0.tgz' 1149 | /hpkp/2.0.0: 1150 | dev: false 1151 | resolution: 1152 | integrity: sha1-EOFCJk52IVpdMMROxD3mTe5tFnI= 1153 | tarball: 'http://registry.npm.taobao.org/hpkp/download/hpkp-2.0.0.tgz' 1154 | /hsts/2.1.0: 1155 | dev: false 1156 | resolution: 1157 | integrity: sha1-y9bJGKI4X+4d1WgL+ys6GUwBIcw= 1158 | tarball: 'http://registry.npm.taobao.org/hsts/download/hsts-2.1.0.tgz' 1159 | /http-assert/1.3.0: 1160 | dependencies: 1161 | deep-equal: 1.0.1 1162 | http-errors: 1.6.2 1163 | engines: 1164 | node: '>= 0.8' 1165 | resolution: 1166 | integrity: sha1-oxpc+IyHPsu1eWkH1NbxMujAHko= 1167 | tarball: 'http://registry.npm.taobao.org/http-assert/download/http-assert-1.3.0.tgz' 1168 | /http-errors/1.6.2: 1169 | dependencies: 1170 | depd: 1.1.1 1171 | inherits: 2.0.3 1172 | setprototypeof: 1.0.3 1173 | statuses: 1.4.0 1174 | engines: 1175 | node: '>= 0.6' 1176 | resolution: 1177 | integrity: sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY= 1178 | tarball: 'http://registry.npm.taobao.org/http-errors/download/http-errors-1.6.2.tgz' 1179 | /humanize-number/0.0.2: 1180 | dev: false 1181 | resolution: 1182 | integrity: sha1-EcCvakcWQ2M1iFiASPF5lUFInBg= 1183 | tarball: 'http://registry.npm.taobao.org/humanize-number/download/humanize-number-0.0.2.tgz' 1184 | /iconv-lite/0.4.19: 1185 | engines: 1186 | node: '>=0.10.0' 1187 | resolution: 1188 | integrity: sha1-90aPYBNfXl2tM5nAqBvpoWA6CCs= 1189 | tarball: 'http://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.19.tgz' 1190 | /iconv-lite/0.4.23: 1191 | dependencies: 1192 | safer-buffer: 2.1.2 1193 | dev: false 1194 | engines: 1195 | node: '>=0.10.0' 1196 | resolution: 1197 | integrity: sha1-KXhx9jvlB63Pv8pxXQzQ7thOmmM= 1198 | tarball: 'http://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.23.tgz' 1199 | /ienoopen/1.0.0: 1200 | dev: false 1201 | resolution: 1202 | integrity: sha1-NGpCj0dKrI9QzzeE6i0PFvYr2ms= 1203 | tarball: 'http://registry.npm.taobao.org/ienoopen/download/ienoopen-1.0.0.tgz' 1204 | /ignore/3.3.7: 1205 | dev: true 1206 | resolution: 1207 | integrity: sha1-YSKJv7PCIOGGpYEYYY1b6MG6sCE= 1208 | tarball: 'http://registry.npm.taobao.org/ignore/download/ignore-3.3.7.tgz' 1209 | /imurmurhash/0.1.4: 1210 | dev: true 1211 | engines: 1212 | node: '>=0.8.19' 1213 | resolution: 1214 | integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o= 1215 | tarball: 'http://registry.npm.taobao.org/imurmurhash/download/imurmurhash-0.1.4.tgz' 1216 | /inflation/2.0.0: 1217 | dev: false 1218 | engines: 1219 | node: '>= 0.8.0' 1220 | resolution: 1221 | integrity: sha1-i0F+R8KPklpFEz2RTKH9OJEH8w8= 1222 | tarball: 'http://registry.npm.taobao.org/inflation/download/inflation-2.0.0.tgz' 1223 | /inflection/1.12.0: 1224 | dev: false 1225 | engines: 1226 | '0': node >= 0.4.0 1227 | resolution: 1228 | integrity: sha1-ogCTVlbW9fa8TcdQLhrstwMihBY= 1229 | tarball: 'http://registry.npm.taobao.org/inflection/download/inflection-1.12.0.tgz' 1230 | /inflight/1.0.6: 1231 | dependencies: 1232 | once: 1.4.0 1233 | wrappy: 1.0.2 1234 | dev: true 1235 | resolution: 1236 | integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 1237 | tarball: 'http://registry.npm.taobao.org/inflight/download/inflight-1.0.6.tgz' 1238 | /inherits/2.0.3: 1239 | resolution: 1240 | integrity: sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 1241 | tarball: 'http://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz' 1242 | /ini/1.3.5: 1243 | dev: false 1244 | resolution: 1245 | integrity: sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc= 1246 | tarball: 'http://registry.npm.taobao.org/ini/download/ini-1.3.5.tgz' 1247 | /inquirer/3.3.0: 1248 | dependencies: 1249 | ansi-escapes: 3.0.0 1250 | chalk: 2.3.1 1251 | cli-cursor: 2.1.0 1252 | cli-width: 2.2.0 1253 | external-editor: 2.1.0 1254 | figures: 2.0.0 1255 | lodash: 4.17.5 1256 | mute-stream: 0.0.7 1257 | run-async: 2.3.0 1258 | rx-lite: 4.0.8 1259 | rx-lite-aggregates: 4.0.8 1260 | string-width: 2.1.1 1261 | strip-ansi: 4.0.0 1262 | through: 2.3.8 1263 | dev: true 1264 | resolution: 1265 | integrity: sha1-ndLyrXZdyrH/BEO0kUQqILoifck= 1266 | tarball: 'http://registry.npm.taobao.org/inquirer/download/inquirer-3.3.0.tgz' 1267 | /invert-kv/1.0.0: 1268 | dev: false 1269 | engines: 1270 | node: '>=0.10.0' 1271 | resolution: 1272 | integrity: sha1-EEqOSqym09jNFXqO+L+rLXo//bY= 1273 | tarball: 'http://registry.npm.taobao.org/invert-kv/download/invert-kv-1.0.0.tgz' 1274 | /is-arrayish/0.2.1: 1275 | dev: true 1276 | resolution: 1277 | integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= 1278 | tarball: 'http://registry.npm.taobao.org/is-arrayish/download/is-arrayish-0.2.1.tgz' 1279 | /is-bluebird/1.0.2: 1280 | dev: false 1281 | engines: 1282 | node: '>=0.10.0' 1283 | resolution: 1284 | integrity: sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI= 1285 | tarball: 'http://registry.npm.taobao.org/is-bluebird/download/is-bluebird-1.0.2.tgz' 1286 | /is-buffer/1.1.6: 1287 | dev: false 1288 | resolution: 1289 | integrity: sha1-76ouqdqg16suoTqXsritUf776L4= 1290 | tarball: 'http://registry.npm.taobao.org/is-buffer/download/is-buffer-1.1.6.tgz' 1291 | /is-builtin-module/1.0.0: 1292 | dependencies: 1293 | builtin-modules: 1.1.1 1294 | dev: true 1295 | engines: 1296 | node: '>=0.10.0' 1297 | resolution: 1298 | integrity: sha1-VAVy0096wxGfj3bDDLwbHgN6/74= 1299 | tarball: 'http://registry.npm.taobao.org/is-builtin-module/download/is-builtin-module-1.0.0.tgz' 1300 | /is-fullwidth-code-point/1.0.0: 1301 | dependencies: 1302 | number-is-nan: 1.0.1 1303 | dev: false 1304 | engines: 1305 | node: '>=0.10.0' 1306 | resolution: 1307 | integrity: sha1-754xOG8DGn8NZDr4L95QxFfvAMs= 1308 | tarball: 'http://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-1.0.0.tgz' 1309 | /is-fullwidth-code-point/2.0.0: 1310 | dev: true 1311 | engines: 1312 | node: '>=4' 1313 | resolution: 1314 | integrity: sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= 1315 | tarball: 'http://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-2.0.0.tgz' 1316 | /is-generator-function/1.0.7: 1317 | engines: 1318 | node: '>= 0.4' 1319 | resolution: 1320 | integrity: sha1-0hMuUpuwAAp/gHlNS99c1eWBNSI= 1321 | tarball: 'http://registry.npm.taobao.org/is-generator-function/download/is-generator-function-1.0.7.tgz' 1322 | /is-path-cwd/1.0.0: 1323 | dev: true 1324 | engines: 1325 | node: '>=0.10.0' 1326 | resolution: 1327 | integrity: sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0= 1328 | tarball: 'http://registry.npm.taobao.org/is-path-cwd/download/is-path-cwd-1.0.0.tgz' 1329 | /is-path-in-cwd/1.0.0: 1330 | dependencies: 1331 | is-path-inside: 1.0.1 1332 | dev: true 1333 | engines: 1334 | node: '>=0.10.0' 1335 | resolution: 1336 | integrity: sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw= 1337 | tarball: 'http://registry.npm.taobao.org/is-path-in-cwd/download/is-path-in-cwd-1.0.0.tgz' 1338 | /is-path-inside/1.0.1: 1339 | dependencies: 1340 | path-is-inside: 1.0.2 1341 | dev: true 1342 | engines: 1343 | node: '>=0.10.0' 1344 | resolution: 1345 | integrity: sha1-jvW33lBDej/cprToZe96pVy0gDY= 1346 | tarball: 'http://registry.npm.taobao.org/is-path-inside/download/is-path-inside-1.0.1.tgz' 1347 | /is-promise/2.1.0: 1348 | dev: true 1349 | resolution: 1350 | integrity: sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= 1351 | tarball: 'http://registry.npm.taobao.org/is-promise/download/is-promise-2.1.0.tgz' 1352 | /is-resolvable/1.1.0: 1353 | dev: true 1354 | resolution: 1355 | integrity: sha1-+xj4fOH+uSUWnJpAfBkxijIG7Yg= 1356 | tarball: 'http://registry.npm.taobao.org/is-resolvable/download/is-resolvable-1.1.0.tgz' 1357 | /isarray/0.0.1: 1358 | dev: false 1359 | resolution: 1360 | integrity: sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= 1361 | tarball: 'http://registry.npm.taobao.org/isarray/download/isarray-0.0.1.tgz' 1362 | /isarray/1.0.0: 1363 | resolution: 1364 | integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= 1365 | tarball: 'http://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz' 1366 | /isexe/2.0.0: 1367 | dev: true 1368 | resolution: 1369 | integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 1370 | tarball: 'http://registry.npm.taobao.org/isexe/download/isexe-2.0.0.tgz' 1371 | /isstream/0.1.2: 1372 | dev: false 1373 | resolution: 1374 | integrity: sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= 1375 | tarball: 'http://registry.npm.taobao.org/isstream/download/isstream-0.1.2.tgz' 1376 | /js-tokens/3.0.2: 1377 | dev: true 1378 | resolution: 1379 | integrity: sha1-mGbfOVECEw449/mWvOtlRDIJwls= 1380 | tarball: 'http://registry.npm.taobao.org/js-tokens/download/js-tokens-3.0.2.tgz' 1381 | /js-yaml/3.10.0: 1382 | dependencies: 1383 | argparse: 1.0.10 1384 | esprima: 4.0.0 1385 | dev: true 1386 | resolution: 1387 | integrity: sha1-LnhEFka9RoLpY/IrbpKCPDCcYtw= 1388 | tarball: 'http://registry.npm.taobao.org/js-yaml/download/js-yaml-3.10.0.tgz' 1389 | /json-schema-traverse/0.3.1: 1390 | dev: true 1391 | resolution: 1392 | integrity: sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= 1393 | tarball: 'http://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.3.1.tgz' 1394 | /json-stable-stringify-without-jsonify/1.0.1: 1395 | dev: true 1396 | resolution: 1397 | integrity: sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= 1398 | tarball: 'http://registry.npm.taobao.org/json-stable-stringify-without-jsonify/download/json-stable-stringify-without-jsonify-1.0.1.tgz' 1399 | /json-stringify-safe/5.0.1: 1400 | dev: false 1401 | resolution: 1402 | integrity: sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= 1403 | tarball: 'http://registry.npm.taobao.org/json-stringify-safe/download/json-stringify-safe-5.0.1.tgz' 1404 | /kcors/2.2.1: 1405 | dev: false 1406 | engines: 1407 | node: '>= 4.3.1' 1408 | resolution: 1409 | integrity: sha1-cWCpTy6uYzQ20s746t0M4jI4Z3k= 1410 | tarball: 'http://registry.npm.taobao.org/kcors/download/kcors-2.2.1.tgz' 1411 | /keygrip/1.0.2: 1412 | engines: 1413 | node: '>= 0.6' 1414 | resolution: 1415 | integrity: sha1-rTKXxVcGneqLz+ek+kkbdcXd65E= 1416 | tarball: 'http://registry.npm.taobao.org/keygrip/download/keygrip-1.0.2.tgz' 1417 | /koa-bodyparser/4.2.0: 1418 | dependencies: 1419 | co-body: 5.1.1 1420 | copy-to: 2.0.1 1421 | dev: false 1422 | engines: 1423 | node: '>=7.6' 1424 | resolution: 1425 | integrity: sha1-vObgi8Zfhwm20fqpQRx/DYk4qlQ= 1426 | tarball: 'http://registry.npm.taobao.org/koa-bodyparser/download/koa-bodyparser-4.2.0.tgz' 1427 | /koa-compose/3.2.1: 1428 | dependencies: 1429 | any-promise: 1.3.0 1430 | resolution: 1431 | integrity: sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec= 1432 | tarball: 'http://registry.npm.taobao.org/koa-compose/download/koa-compose-3.2.1.tgz' 1433 | /koa-compose/4.0.0: 1434 | resolution: 1435 | integrity: sha1-KAClE9nDYe8NY4UrA45Pby1adzw= 1436 | tarball: 'http://registry.npm.taobao.org/koa-compose/download/koa-compose-4.0.0.tgz' 1437 | /koa-compress/2.0.0: 1438 | dependencies: 1439 | bytes: 2.5.0 1440 | compressible: 2.0.13 1441 | koa-is-json: 1.0.0 1442 | statuses: 1.4.0 1443 | dev: false 1444 | resolution: 1445 | integrity: sha1-e36ykhuEd0a14SK6n1zYpnHo6jo= 1446 | tarball: 'http://registry.npm.taobao.org/koa-compress/download/koa-compress-2.0.0.tgz' 1447 | /koa-convert/1.2.0: 1448 | dependencies: 1449 | co: 4.6.0 1450 | koa-compose: 3.2.1 1451 | engines: 1452 | node: '>= 4' 1453 | resolution: 1454 | integrity: sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA= 1455 | tarball: 'http://registry.npm.taobao.org/koa-convert/download/koa-convert-1.2.0.tgz' 1456 | /koa-favicon/2.0.0: 1457 | dependencies: 1458 | mz: 2.7.0 1459 | dev: false 1460 | resolution: 1461 | integrity: sha1-HE5QI+cfy/pkxZ3vAK4Q5pQuWm4= 1462 | tarball: 'http://registry.npm.taobao.org/koa-favicon/download/koa-favicon-2.0.0.tgz' 1463 | /koa-helmet/3.3.0: 1464 | dependencies: 1465 | helmet: 3.11.0 1466 | dev: false 1467 | engines: 1468 | node: '>= 4.0.0' 1469 | resolution: 1470 | integrity: sha1-KP5ajH7V8PNkEMLoyZ6rcDHntxQ= 1471 | tarball: 'http://registry.npm.taobao.org/koa-helmet/download/koa-helmet-3.3.0.tgz' 1472 | /koa-is-json/1.0.0: 1473 | resolution: 1474 | integrity: sha1-JzwH7c3Ljfaiwat9We52SRRR7BQ= 1475 | tarball: 'http://registry.npm.taobao.org/koa-is-json/download/koa-is-json-1.0.0.tgz' 1476 | /koa-json/2.0.2: 1477 | dependencies: 1478 | koa-is-json: 1.0.0 1479 | streaming-json-stringify: 3.1.0 1480 | dev: false 1481 | resolution: 1482 | integrity: sha1-Nq8U5uofXWRtfESihXAcb4Wk/eQ= 1483 | tarball: 'http://registry.npm.taobao.org/koa-json/download/koa-json-2.0.2.tgz' 1484 | /koa-jsonp/2.0.2: 1485 | dev: false 1486 | resolution: 1487 | integrity: sha1-Q5QhRAP6z9YdffJGYpLJ+w9r8II= 1488 | tarball: 'http://registry.npm.taobao.org/koa-jsonp/download/koa-jsonp-2.0.2.tgz' 1489 | /koa-livereload/0.2.0: 1490 | dependencies: 1491 | koa: 2.5.0 1492 | stream-injecter: 0.0.1 1493 | dev: true 1494 | resolution: 1495 | integrity: sha1-brTKkQpcePQFDLKDBpWKG3Fgqy4= 1496 | tarball: 'http://registry.npm.taobao.org/koa-livereload/download/koa-livereload-0.2.0.tgz' 1497 | /koa-query-pretty/0.3.0: 1498 | dev: false 1499 | resolution: 1500 | integrity: sha1-CgQ4Sh+QKi5rziQ+OHOZESfc0XI= 1501 | tarball: 'http://registry.npm.taobao.org/koa-query-pretty/download/koa-query-pretty-0.3.0.tgz' 1502 | /koa-router/7.4.0: 1503 | dependencies: 1504 | debug: 3.1.0 1505 | http-errors: 1.6.2 1506 | koa-compose: 3.2.1 1507 | methods: 1.1.2 1508 | path-to-regexp: 1.7.0 1509 | urijs: 1.19.1 1510 | dev: false 1511 | engines: 1512 | node: '>= 4' 1513 | resolution: 1514 | integrity: sha1-ruH3rcAtXLMdfWdGXJ6syCXoxeA= 1515 | tarball: 'http://registry.npm.taobao.org/koa-router/download/koa-router-7.4.0.tgz' 1516 | /koa/2.5.0: 1517 | dependencies: 1518 | accepts: 1.3.4 1519 | content-disposition: 0.5.2 1520 | content-type: 1.0.4 1521 | cookies: 0.7.1 1522 | debug: 3.1.0 1523 | delegates: 1.0.0 1524 | depd: 1.1.2 1525 | destroy: 1.0.4 1526 | error-inject: 1.0.0 1527 | escape-html: 1.0.3 1528 | fresh: 0.5.2 1529 | http-assert: 1.3.0 1530 | http-errors: 1.6.2 1531 | is-generator-function: 1.0.7 1532 | koa-compose: 4.0.0 1533 | koa-convert: 1.2.0 1534 | koa-is-json: 1.0.0 1535 | mime-types: 2.1.18 1536 | on-finished: 2.3.0 1537 | only: 0.0.2 1538 | parseurl: 1.3.2 1539 | statuses: 1.4.0 1540 | type-is: 1.6.16 1541 | vary: 1.1.2 1542 | engines: 1543 | node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4 1544 | resolution: 1545 | integrity: sha1-sPvh4ZXkOydYigT9C+DdrsosFUw= 1546 | tarball: 'http://registry.npm.taobao.org/koa/download/koa-2.5.0.tgz' 1547 | /lcid/1.0.0: 1548 | dependencies: 1549 | invert-kv: 1.0.0 1550 | dev: false 1551 | engines: 1552 | node: '>=0.10.0' 1553 | resolution: 1554 | integrity: sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= 1555 | tarball: 'http://registry.npm.taobao.org/lcid/download/lcid-1.0.0.tgz' 1556 | /levn/0.3.0: 1557 | dependencies: 1558 | prelude-ls: 1.1.2 1559 | type-check: 0.3.2 1560 | dev: true 1561 | engines: 1562 | node: '>= 0.8.0' 1563 | resolution: 1564 | integrity: sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= 1565 | tarball: 'http://registry.npm.taobao.org/levn/download/levn-0.3.0.tgz' 1566 | /load-json-file/2.0.0: 1567 | dependencies: 1568 | graceful-fs: 4.1.11 1569 | parse-json: 2.2.0 1570 | pify: 2.3.0 1571 | strip-bom: 3.0.0 1572 | dev: true 1573 | engines: 1574 | node: '>=4' 1575 | resolution: 1576 | integrity: sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= 1577 | tarball: 'http://registry.npm.taobao.org/load-json-file/download/load-json-file-2.0.0.tgz' 1578 | /locate-path/2.0.0: 1579 | dependencies: 1580 | p-locate: 2.0.0 1581 | path-exists: 3.0.0 1582 | dev: true 1583 | engines: 1584 | node: '>=4' 1585 | resolution: 1586 | integrity: sha1-K1aLJl7slExtnA3pw9u7ygNUzY4= 1587 | tarball: 'http://registry.npm.taobao.org/locate-path/download/locate-path-2.0.0.tgz' 1588 | /lodash.compact/3.0.1: 1589 | dev: false 1590 | resolution: 1591 | integrity: sha1-VAzjg3dFl1gHRx4WtKK6IeclbKU= 1592 | tarball: 'http://registry.npm.taobao.org/lodash.compact/download/lodash.compact-3.0.1.tgz' 1593 | /lodash.cond/4.5.2: 1594 | dev: true 1595 | resolution: 1596 | integrity: sha1-9HGh2khr5g9quVXRcRVSPdHSVdU= 1597 | tarball: 'http://registry.npm.taobao.org/lodash.cond/download/lodash.cond-4.5.2.tgz' 1598 | /lodash.curry/4.1.1: 1599 | dev: false 1600 | resolution: 1601 | integrity: sha1-JI42By7ekGUB11lmIAqG2riyMXA= 1602 | tarball: 'http://registry.npm.taobao.org/lodash.curry/download/lodash.curry-4.1.1.tgz' 1603 | /lodash.reduce/4.6.0: 1604 | dev: false 1605 | resolution: 1606 | integrity: sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs= 1607 | tarball: 'http://registry.npm.taobao.org/lodash.reduce/download/lodash.reduce-4.6.0.tgz' 1608 | /lodash/4.17.5: 1609 | resolution: 1610 | integrity: sha1-maktZcAnLevoyWtgV7yPv6O+1RE= 1611 | tarball: 'http://registry.npm.taobao.org/lodash/download/lodash-4.17.5.tgz' 1612 | /long/4.0.0: 1613 | dev: false 1614 | resolution: 1615 | integrity: sha1-mntxz7fTYaGU6lVSQckvdGjVvyg= 1616 | tarball: 'http://registry.npm.taobao.org/long/download/long-4.0.0.tgz' 1617 | /lru-cache/2.5.0: 1618 | dev: false 1619 | resolution: 1620 | integrity: sha1-2COIrpyWC+y+oMc7uet5tsbOmus= 1621 | tarball: 'http://registry.npm.taobao.org/lru-cache/download/lru-cache-2.5.0.tgz' 1622 | /lru-cache/4.1.1: 1623 | dependencies: 1624 | pseudomap: 1.0.2 1625 | yallist: 2.1.2 1626 | resolution: 1627 | integrity: sha1-Yi4y6CSItJJ5EUpPns9F581rulU= 1628 | tarball: 'http://registry.npm.taobao.org/lru-cache/download/lru-cache-4.1.1.tgz' 1629 | /media-typer/0.3.0: 1630 | engines: 1631 | node: '>= 0.6' 1632 | resolution: 1633 | integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= 1634 | tarball: 'http://registry.npm.taobao.org/media-typer/download/media-typer-0.3.0.tgz' 1635 | /methods/1.1.2: 1636 | dev: false 1637 | engines: 1638 | node: '>= 0.6' 1639 | resolution: 1640 | integrity: sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= 1641 | tarball: 'http://registry.npm.taobao.org/methods/download/methods-1.1.2.tgz' 1642 | /mime-db/1.33.0: 1643 | engines: 1644 | node: '>= 0.6' 1645 | resolution: 1646 | integrity: sha1-o0kgUKXLm2NFBUHjnZeI0icng9s= 1647 | tarball: 'http://registry.npm.taobao.org/mime-db/download/mime-db-1.33.0.tgz' 1648 | /mime-types/2.1.18: 1649 | dependencies: 1650 | mime-db: 1.33.0 1651 | engines: 1652 | node: '>= 0.6' 1653 | resolution: 1654 | integrity: sha1-bzI/YKg9ERRvgx/xH9ZuL+VQO7g= 1655 | tarball: 'http://registry.npm.taobao.org/mime-types/download/mime-types-2.1.18.tgz' 1656 | /mimic-fn/1.2.0: 1657 | dev: true 1658 | engines: 1659 | node: '>=4' 1660 | resolution: 1661 | integrity: sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI= 1662 | tarball: 'http://registry.npm.taobao.org/mimic-fn/download/mimic-fn-1.2.0.tgz' 1663 | /minimatch/3.0.4: 1664 | dependencies: 1665 | brace-expansion: 1.1.11 1666 | dev: true 1667 | resolution: 1668 | integrity: sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM= 1669 | tarball: 'http://registry.npm.taobao.org/minimatch/download/minimatch-3.0.4.tgz' 1670 | /minimist/0.0.8: 1671 | dev: true 1672 | resolution: 1673 | integrity: sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= 1674 | tarball: 'http://registry.npm.taobao.org/minimist/download/minimist-0.0.8.tgz' 1675 | /mkdirp/0.5.1: 1676 | dependencies: 1677 | minimist: 0.0.8 1678 | dev: true 1679 | resolution: 1680 | integrity: sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= 1681 | tarball: 'http://registry.npm.taobao.org/mkdirp/download/mkdirp-0.5.1.tgz' 1682 | /mocha/5.0.1: 1683 | dependencies: 1684 | browser-stdout: 1.3.0 1685 | commander: 2.11.0 1686 | debug: 3.1.0 1687 | diff: 3.3.1 1688 | escape-string-regexp: 1.0.5 1689 | glob: 7.1.2 1690 | growl: 1.10.3 1691 | he: 1.1.1 1692 | mkdirp: 0.5.1 1693 | supports-color: 4.4.0 1694 | dev: true 1695 | engines: 1696 | node: '>= 4.0.0' 1697 | resolution: 1698 | integrity: sha1-dZtiyDawcyOCpitrH7JF7BvJQ6w= 1699 | tarball: 'http://registry.npm.taobao.org/mocha/download/mocha-5.0.1.tgz' 1700 | /moment-timezone/0.5.14: 1701 | dependencies: 1702 | moment: 2.20.1 1703 | dev: false 1704 | resolution: 1705 | integrity: sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE= 1706 | tarball: 'http://registry.npm.taobao.org/moment-timezone/download/moment-timezone-0.5.14.tgz' 1707 | /moment/2.20.1: 1708 | dev: false 1709 | resolution: 1710 | integrity: sha1-1usaRsvMFKKy+UNBEsH/iQfzE/0= 1711 | tarball: 'http://registry.npm.taobao.org/moment/download/moment-2.20.1.tgz' 1712 | /ms/2.0.0: 1713 | resolution: 1714 | integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 1715 | tarball: 'http://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz' 1716 | /mute-stream/0.0.7: 1717 | dev: true 1718 | resolution: 1719 | integrity: sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= 1720 | tarball: 'http://registry.npm.taobao.org/mute-stream/download/mute-stream-0.0.7.tgz' 1721 | /mysql2/1.5.2: 1722 | dependencies: 1723 | cardinal: 1.0.0 1724 | denque: 1.2.3 1725 | generate-function: 2.0.0 1726 | iconv-lite: 0.4.19 1727 | long: 4.0.0 1728 | lru-cache: 4.1.1 1729 | named-placeholders: 1.1.1 1730 | object-assign: 4.1.1 1731 | readable-stream: 2.3.2 1732 | safe-buffer: 5.1.1 1733 | seq-queue: 0.0.5 1734 | sqlstring: 2.3.0 1735 | dev: false 1736 | engines: 1737 | node: '>= 4.0' 1738 | resolution: 1739 | integrity: sha1-5OBzg5uxCXJmmyQOx3Bh1/9Vz1k= 1740 | tarball: 'http://registry.npm.taobao.org/mysql2/download/mysql2-1.5.2.tgz' 1741 | /mz/2.7.0: 1742 | dependencies: 1743 | any-promise: 1.3.0 1744 | object-assign: 4.1.1 1745 | thenify-all: 1.6.0 1746 | dev: false 1747 | resolution: 1748 | integrity: sha1-lQCAV6Vsr63CvGPd5/n/aVWUjjI= 1749 | tarball: 'http://registry.npm.taobao.org/mz/download/mz-2.7.0.tgz' 1750 | /named-placeholders/1.1.1: 1751 | dependencies: 1752 | lru-cache: 2.5.0 1753 | dev: false 1754 | resolution: 1755 | integrity: sha1-O3oNJiA910s6nfTJz7gnsvuQfmQ= 1756 | tarball: 'http://registry.npm.taobao.org/named-placeholders/download/named-placeholders-1.1.1.tgz' 1757 | /natural-compare/1.4.0: 1758 | dev: true 1759 | resolution: 1760 | integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= 1761 | tarball: 'http://registry.npm.taobao.org/natural-compare/download/natural-compare-1.4.0.tgz' 1762 | /nconf/0.10.0: 1763 | dependencies: 1764 | async: 1.5.2 1765 | ini: 1.3.5 1766 | secure-keys: 1.0.0 1767 | yargs: 3.32.0 1768 | dev: false 1769 | engines: 1770 | node: '>= 0.4.0' 1771 | resolution: 1772 | integrity: sha1-2hKF7pXQqSLKbO51rc+GH0ggWtI= 1773 | tarball: 'http://registry.npm.taobao.org/nconf/download/nconf-0.10.0.tgz' 1774 | /negotiator/0.6.1: 1775 | engines: 1776 | node: '>= 0.6' 1777 | resolution: 1778 | integrity: sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= 1779 | tarball: 'http://registry.npm.taobao.org/negotiator/download/negotiator-0.6.1.tgz' 1780 | /netease-music-sdk/0.3.1: 1781 | dependencies: 1782 | axios: 0.17.1 1783 | big-integer: 1.6.28 1784 | dev: false 1785 | resolution: 1786 | integrity: sha1-naYWg3Tf1TzejnGuIret3U/+Scc= 1787 | tarball: 'http://registry.npm.taobao.org/netease-music-sdk/download/netease-music-sdk-0.3.1.tgz' 1788 | /nocache/2.0.0: 1789 | dev: false 1790 | resolution: 1791 | integrity: sha1-ICtIAhoMTL3i34DeFaF0Q8i0OYA= 1792 | tarball: 'http://registry.npm.taobao.org/nocache/download/nocache-2.0.0.tgz' 1793 | /nodemailer/4.4.2: 1794 | dev: false 1795 | engines: 1796 | node: '>=6.0.0' 1797 | resolution: 1798 | integrity: sha1-8hX7iOihBS+fkwg5CeEW0refyN4= 1799 | tarball: 'http://registry.npm.taobao.org/nodemailer/download/nodemailer-4.4.2.tgz' 1800 | /normalize-package-data/2.4.0: 1801 | dependencies: 1802 | hosted-git-info: 2.5.0 1803 | is-builtin-module: 1.0.0 1804 | semver: 5.5.0 1805 | validate-npm-package-license: 3.0.1 1806 | dev: true 1807 | resolution: 1808 | integrity: sha1-EvlaMH1YNSB1oEkHuErIvpisAS8= 1809 | tarball: 'http://registry.npm.taobao.org/normalize-package-data/download/normalize-package-data-2.4.0.tgz' 1810 | /number-is-nan/1.0.1: 1811 | dev: false 1812 | engines: 1813 | node: '>=0.10.0' 1814 | resolution: 1815 | integrity: sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= 1816 | tarball: 'http://registry.npm.taobao.org/number-is-nan/download/number-is-nan-1.0.1.tgz' 1817 | /nyc/11.4.1: 1818 | bundledDependencies: 1819 | - archy 1820 | - arrify 1821 | - caching-transform 1822 | - convert-source-map 1823 | - debug-log 1824 | - default-require-extensions 1825 | - find-cache-dir 1826 | - find-up 1827 | - foreground-child 1828 | - glob 1829 | - istanbul-lib-coverage 1830 | - istanbul-lib-hook 1831 | - istanbul-lib-instrument 1832 | - istanbul-lib-report 1833 | - istanbul-lib-source-maps 1834 | - istanbul-reports 1835 | - md5-hex 1836 | - merge-source-map 1837 | - micromatch 1838 | - mkdirp 1839 | - resolve-from 1840 | - rimraf 1841 | - signal-exit 1842 | - spawn-wrap 1843 | - test-exclude 1844 | - yargs 1845 | - yargs-parser 1846 | dev: true 1847 | resolution: 1848 | integrity: sha1-E/335+8i0CfGHRdHWPaXimj09eU= 1849 | tarball: 'http://registry.npm.taobao.org/nyc/download/nyc-11.4.1.tgz' 1850 | /object-assign/4.1.1: 1851 | engines: 1852 | node: '>=0.10.0' 1853 | resolution: 1854 | integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 1855 | tarball: 'http://registry.npm.taobao.org/object-assign/download/object-assign-4.1.1.tgz' 1856 | /on-finished/2.3.0: 1857 | dependencies: 1858 | ee-first: 1.1.1 1859 | engines: 1860 | node: '>= 0.8' 1861 | resolution: 1862 | integrity: sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= 1863 | tarball: 'http://registry.npm.taobao.org/on-finished/download/on-finished-2.3.0.tgz' 1864 | /once/1.4.0: 1865 | dependencies: 1866 | wrappy: 1.0.2 1867 | dev: true 1868 | resolution: 1869 | integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 1870 | tarball: 'http://registry.npm.taobao.org/once/download/once-1.4.0.tgz' 1871 | /onetime/2.0.1: 1872 | dependencies: 1873 | mimic-fn: 1.2.0 1874 | dev: true 1875 | engines: 1876 | node: '>=4' 1877 | resolution: 1878 | integrity: sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= 1879 | tarball: 'http://registry.npm.taobao.org/onetime/download/onetime-2.0.1.tgz' 1880 | /only/0.0.2: 1881 | resolution: 1882 | integrity: sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q= 1883 | tarball: 'http://registry.npm.taobao.org/only/download/only-0.0.2.tgz' 1884 | /optionator/0.8.2: 1885 | dependencies: 1886 | deep-is: 0.1.3 1887 | fast-levenshtein: 2.0.6 1888 | levn: 0.3.0 1889 | prelude-ls: 1.1.2 1890 | type-check: 0.3.2 1891 | wordwrap: 1.0.0 1892 | dev: true 1893 | engines: 1894 | node: '>= 0.8.0' 1895 | resolution: 1896 | integrity: sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= 1897 | tarball: 'http://registry.npm.taobao.org/optionator/download/optionator-0.8.2.tgz' 1898 | /os-locale/1.4.0: 1899 | dependencies: 1900 | lcid: 1.0.0 1901 | dev: false 1902 | engines: 1903 | node: '>=0.10.0' 1904 | resolution: 1905 | integrity: sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= 1906 | tarball: 'http://registry.npm.taobao.org/os-locale/download/os-locale-1.4.0.tgz' 1907 | /os-tmpdir/1.0.2: 1908 | dev: true 1909 | engines: 1910 | node: '>=0.10.0' 1911 | resolution: 1912 | integrity: sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= 1913 | tarball: 'http://registry.npm.taobao.org/os-tmpdir/download/os-tmpdir-1.0.2.tgz' 1914 | /p-limit/1.2.0: 1915 | dependencies: 1916 | p-try: 1.0.0 1917 | dev: true 1918 | engines: 1919 | node: '>=4' 1920 | resolution: 1921 | integrity: sha1-DpK2vty1nwIsE9DxlJ3ILRWQnxw= 1922 | tarball: 'http://registry.npm.taobao.org/p-limit/download/p-limit-1.2.0.tgz' 1923 | /p-locate/2.0.0: 1924 | dependencies: 1925 | p-limit: 1.2.0 1926 | dev: true 1927 | engines: 1928 | node: '>=4' 1929 | resolution: 1930 | integrity: sha1-IKAQOyIqcMj9OcwuWAaA893l7EM= 1931 | tarball: 'http://registry.npm.taobao.org/p-locate/download/p-locate-2.0.0.tgz' 1932 | /p-try/1.0.0: 1933 | dev: true 1934 | engines: 1935 | node: '>=4' 1936 | resolution: 1937 | integrity: sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= 1938 | tarball: 'http://registry.npm.taobao.org/p-try/download/p-try-1.0.0.tgz' 1939 | /parse-json/2.2.0: 1940 | dependencies: 1941 | error-ex: 1.3.1 1942 | dev: true 1943 | engines: 1944 | node: '>=0.10.0' 1945 | resolution: 1946 | integrity: sha1-9ID0BDTvgHQfhGkJn43qGPVaTck= 1947 | tarball: 'http://registry.npm.taobao.org/parse-json/download/parse-json-2.2.0.tgz' 1948 | /parseurl/1.3.2: 1949 | engines: 1950 | node: '>= 0.8' 1951 | resolution: 1952 | integrity: sha1-/CidTtiZMRlGDBViUyYs3I3mW/M= 1953 | tarball: 'http://registry.npm.taobao.org/parseurl/download/parseurl-1.3.2.tgz' 1954 | /passthrough-counter/1.0.0: 1955 | dev: false 1956 | resolution: 1957 | integrity: sha1-GWfZ5m2lcrXAI8eH2xEqOHqxZvo= 1958 | tarball: 'http://registry.npm.taobao.org/passthrough-counter/download/passthrough-counter-1.0.0.tgz' 1959 | /path-exists/2.1.0: 1960 | dependencies: 1961 | pinkie-promise: 2.0.1 1962 | dev: true 1963 | engines: 1964 | node: '>=0.10.0' 1965 | resolution: 1966 | integrity: sha1-D+tsZPD8UY2adU3V77YscCJ2H0s= 1967 | tarball: 'http://registry.npm.taobao.org/path-exists/download/path-exists-2.1.0.tgz' 1968 | /path-exists/3.0.0: 1969 | dev: true 1970 | engines: 1971 | node: '>=4' 1972 | resolution: 1973 | integrity: sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= 1974 | tarball: 'http://registry.npm.taobao.org/path-exists/download/path-exists-3.0.0.tgz' 1975 | /path-is-absolute/1.0.1: 1976 | dev: true 1977 | engines: 1978 | node: '>=0.10.0' 1979 | resolution: 1980 | integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 1981 | tarball: 'http://registry.npm.taobao.org/path-is-absolute/download/path-is-absolute-1.0.1.tgz' 1982 | /path-is-inside/1.0.2: 1983 | dev: true 1984 | resolution: 1985 | integrity: sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= 1986 | tarball: 'http://registry.npm.taobao.org/path-is-inside/download/path-is-inside-1.0.2.tgz' 1987 | /path-parse/1.0.5: 1988 | dev: true 1989 | resolution: 1990 | integrity: sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME= 1991 | tarball: 'http://registry.npm.taobao.org/path-parse/download/path-parse-1.0.5.tgz' 1992 | /path-to-regexp/1.7.0: 1993 | dependencies: 1994 | isarray: 0.0.1 1995 | dev: false 1996 | resolution: 1997 | integrity: sha1-Wf3g9DW62suhA6hOnTvGTpa5k30= 1998 | tarball: 'http://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-1.7.0.tgz' 1999 | /path-type/2.0.0: 2000 | dependencies: 2001 | pify: 2.3.0 2002 | dev: true 2003 | engines: 2004 | node: '>=4' 2005 | resolution: 2006 | integrity: sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= 2007 | tarball: 'http://registry.npm.taobao.org/path-type/download/path-type-2.0.0.tgz' 2008 | /pathval/1.1.0: 2009 | dev: true 2010 | resolution: 2011 | integrity: sha1-uULm1L3mUwBe9rcTYd74cn0GReA= 2012 | tarball: 'http://registry.npm.taobao.org/pathval/download/pathval-1.1.0.tgz' 2013 | /pify/2.3.0: 2014 | dev: true 2015 | engines: 2016 | node: '>=0.10.0' 2017 | resolution: 2018 | integrity: sha1-7RQaasBDqEnqWISY59yosVMw6Qw= 2019 | tarball: 'http://registry.npm.taobao.org/pify/download/pify-2.3.0.tgz' 2020 | /pify/3.0.0: 2021 | dev: false 2022 | engines: 2023 | node: '>=4' 2024 | resolution: 2025 | integrity: sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= 2026 | tarball: 'http://registry.npm.taobao.org/pify/download/pify-3.0.0.tgz' 2027 | /pinkie-promise/2.0.1: 2028 | dependencies: 2029 | pinkie: 2.0.4 2030 | dev: true 2031 | engines: 2032 | node: '>=0.10.0' 2033 | resolution: 2034 | integrity: sha1-ITXW36ejWMBprJsXh3YogihFD/o= 2035 | tarball: 'http://registry.npm.taobao.org/pinkie-promise/download/pinkie-promise-2.0.1.tgz' 2036 | /pinkie/2.0.4: 2037 | dev: true 2038 | engines: 2039 | node: '>=0.10.0' 2040 | resolution: 2041 | integrity: sha1-clVrgM+g1IqXToDnckjoDtT3+HA= 2042 | tarball: 'http://registry.npm.taobao.org/pinkie/download/pinkie-2.0.4.tgz' 2043 | /pkg-dir/1.0.0: 2044 | dependencies: 2045 | find-up: 1.1.2 2046 | dev: true 2047 | engines: 2048 | node: '>=0.10.0' 2049 | resolution: 2050 | integrity: sha1-ektQio1bstYp1EcFb/TpyTFM89Q= 2051 | tarball: 'http://registry.npm.taobao.org/pkg-dir/download/pkg-dir-1.0.0.tgz' 2052 | /platform/1.3.5: 2053 | dev: false 2054 | resolution: 2055 | integrity: sha1-+2lYxpbgfikY0u7aDwvJRI1zNEQ= 2056 | tarball: 'http://registry.npm.taobao.org/platform/download/platform-1.3.5.tgz' 2057 | /pluralize/7.0.0: 2058 | dev: true 2059 | engines: 2060 | node: '>=4' 2061 | resolution: 2062 | integrity: sha1-KYuJ34uTsCIdv0Ia0rGx6iP8Z3c= 2063 | tarball: 'http://registry.npm.taobao.org/pluralize/download/pluralize-7.0.0.tgz' 2064 | /prelude-ls/1.1.2: 2065 | dev: true 2066 | engines: 2067 | node: '>= 0.8.0' 2068 | resolution: 2069 | integrity: sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= 2070 | tarball: 'http://registry.npm.taobao.org/prelude-ls/download/prelude-ls-1.1.2.tgz' 2071 | /process-nextick-args/1.0.7: 2072 | dev: false 2073 | resolution: 2074 | integrity: sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= 2075 | tarball: 'http://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-1.0.7.tgz' 2076 | /process-nextick-args/2.0.0: 2077 | resolution: 2078 | integrity: sha1-o31zL0JxtKsa0HDTVQjoKQeI/6o= 2079 | tarball: 'http://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-2.0.0.tgz' 2080 | /progress/2.0.0: 2081 | dev: true 2082 | engines: 2083 | node: '>=0.4.0' 2084 | resolution: 2085 | integrity: sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8= 2086 | tarball: 'http://registry.npm.taobao.org/progress/download/progress-2.0.0.tgz' 2087 | /pseudomap/1.0.2: 2088 | resolution: 2089 | integrity: sha1-8FKijacOYYkX7wqKw0wa5aaChrM= 2090 | tarball: 'http://registry.npm.taobao.org/pseudomap/download/pseudomap-1.0.2.tgz' 2091 | /qs/6.5.1: 2092 | dev: false 2093 | engines: 2094 | node: '>=0.6' 2095 | resolution: 2096 | integrity: sha1-NJzfbu+J7EXBLX1es/wMhwNDptg= 2097 | tarball: 'http://registry.npm.taobao.org/qs/download/qs-6.5.1.tgz' 2098 | /raw-body/2.3.2: 2099 | dependencies: 2100 | bytes: 3.0.0 2101 | http-errors: 1.6.2 2102 | iconv-lite: 0.4.19 2103 | unpipe: 1.0.0 2104 | dev: false 2105 | engines: 2106 | node: '>= 0.8' 2107 | resolution: 2108 | integrity: sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k= 2109 | tarball: 'http://registry.npm.taobao.org/raw-body/download/raw-body-2.3.2.tgz' 2110 | /read-pkg-up/2.0.0: 2111 | dependencies: 2112 | find-up: 2.1.0 2113 | read-pkg: 2.0.0 2114 | dev: true 2115 | engines: 2116 | node: '>=4' 2117 | resolution: 2118 | integrity: sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= 2119 | tarball: 'http://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-2.0.0.tgz' 2120 | /read-pkg/2.0.0: 2121 | dependencies: 2122 | load-json-file: 2.0.0 2123 | normalize-package-data: 2.4.0 2124 | path-type: 2.0.0 2125 | dev: true 2126 | engines: 2127 | node: '>=4' 2128 | resolution: 2129 | integrity: sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= 2130 | tarball: 'http://registry.npm.taobao.org/read-pkg/download/read-pkg-2.0.0.tgz' 2131 | /readable-stream/2.3.2: 2132 | dependencies: 2133 | core-util-is: 1.0.2 2134 | inherits: 2.0.3 2135 | isarray: 1.0.0 2136 | process-nextick-args: 1.0.7 2137 | safe-buffer: 5.1.1 2138 | string_decoder: 1.0.3 2139 | util-deprecate: 1.0.2 2140 | dev: false 2141 | resolution: 2142 | integrity: sha1-WgTfBeT1f+Pw3Gj90R3FyXx+b00= 2143 | tarball: 'http://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.2.tgz' 2144 | /readable-stream/2.3.4: 2145 | dependencies: 2146 | core-util-is: 1.0.2 2147 | inherits: 2.0.3 2148 | isarray: 1.0.0 2149 | process-nextick-args: 2.0.0 2150 | safe-buffer: 5.1.1 2151 | string_decoder: 1.0.3 2152 | util-deprecate: 1.0.2 2153 | resolution: 2154 | integrity: sha1-yUbD9H+n2Oq8C2FQ9KEvaaRXQHE= 2155 | tarball: 'http://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.4.tgz' 2156 | /redeyed/1.0.1: 2157 | dependencies: 2158 | esprima: 3.0.0 2159 | dev: false 2160 | resolution: 2161 | integrity: sha1-6WwZO0DAgWsArshCaY5hGF5VSYo= 2162 | tarball: 'http://registry.npm.taobao.org/redeyed/download/redeyed-1.0.1.tgz' 2163 | /redis-commands/1.3.1: 2164 | dev: false 2165 | resolution: 2166 | integrity: sha1-gdgm9F+pyLIBH0zXoP5ZfSQdRCs= 2167 | tarball: 'http://registry.npm.taobao.org/redis-commands/download/redis-commands-1.3.1.tgz' 2168 | /redis-parser/2.6.0: 2169 | dev: false 2170 | engines: 2171 | node: '>=0.10.0' 2172 | resolution: 2173 | integrity: sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs= 2174 | tarball: 'http://registry.npm.taobao.org/redis-parser/download/redis-parser-2.6.0.tgz' 2175 | /redis/2.8.0: 2176 | dependencies: 2177 | double-ended-queue: 2.1.0-0 2178 | redis-commands: 1.3.1 2179 | redis-parser: 2.6.0 2180 | dev: false 2181 | engines: 2182 | node: '>=0.10.0' 2183 | resolution: 2184 | integrity: sha1-ICKI4/WMSfYHnZevehDhMDrhSwI= 2185 | tarball: 'http://registry.npm.taobao.org/redis/download/redis-2.8.0.tgz' 2186 | /referrer-policy/1.1.0: 2187 | dev: false 2188 | resolution: 2189 | integrity: sha1-NXdOtzW/UPtsB46DM0tHI1AgfXk= 2190 | tarball: 'http://registry.npm.taobao.org/referrer-policy/download/referrer-policy-1.1.0.tgz' 2191 | /require-uncached/1.0.3: 2192 | dependencies: 2193 | caller-path: 0.1.0 2194 | resolve-from: 1.0.1 2195 | dev: true 2196 | engines: 2197 | node: '>=0.10.0' 2198 | resolution: 2199 | integrity: sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM= 2200 | tarball: 'http://registry.npm.taobao.org/require-uncached/download/require-uncached-1.0.3.tgz' 2201 | /resolve-from/1.0.1: 2202 | dev: true 2203 | engines: 2204 | node: '>=0.10.0' 2205 | resolution: 2206 | integrity: sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY= 2207 | tarball: 'http://registry.npm.taobao.org/resolve-from/download/resolve-from-1.0.1.tgz' 2208 | /resolve/1.5.0: 2209 | dependencies: 2210 | path-parse: 1.0.5 2211 | dev: true 2212 | resolution: 2213 | integrity: sha1-HwmsznlsmnYlefMbLBzEw83fnzY= 2214 | tarball: 'http://registry.npm.taobao.org/resolve/download/resolve-1.5.0.tgz' 2215 | /restore-cursor/2.0.0: 2216 | dependencies: 2217 | onetime: 2.0.1 2218 | signal-exit: 3.0.2 2219 | dev: true 2220 | engines: 2221 | node: '>=4' 2222 | resolution: 2223 | integrity: sha1-n37ih/gv0ybU/RYpI9YhKe7g368= 2224 | tarball: 'http://registry.npm.taobao.org/restore-cursor/download/restore-cursor-2.0.0.tgz' 2225 | /retry-as-promised/2.3.2: 2226 | dependencies: 2227 | bluebird: 3.5.1 2228 | debug: 2.6.9 2229 | dev: false 2230 | resolution: 2231 | integrity: sha1-zZdO5P2bX+A8vzGHHuSCIcB3N7c= 2232 | tarball: 'http://registry.npm.taobao.org/retry-as-promised/download/retry-as-promised-2.3.2.tgz' 2233 | /rimraf/2.6.2: 2234 | dependencies: 2235 | glob: 7.1.2 2236 | dev: true 2237 | resolution: 2238 | integrity: sha1-LtgVDSShbqhlHm1u8PR8QVjOejY= 2239 | tarball: 'http://registry.npm.taobao.org/rimraf/download/rimraf-2.6.2.tgz' 2240 | /run-async/2.3.0: 2241 | dependencies: 2242 | is-promise: 2.1.0 2243 | dev: true 2244 | engines: 2245 | node: '>=0.12.0' 2246 | resolution: 2247 | integrity: sha1-A3GrSuC91yDUFm19/aZP96RFpsA= 2248 | tarball: 'http://registry.npm.taobao.org/run-async/download/run-async-2.3.0.tgz' 2249 | /rx-lite-aggregates/4.0.8: 2250 | dependencies: 2251 | rx-lite: 4.0.8 2252 | dev: true 2253 | resolution: 2254 | integrity: sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74= 2255 | tarball: 'http://registry.npm.taobao.org/rx-lite-aggregates/download/rx-lite-aggregates-4.0.8.tgz' 2256 | /rx-lite/4.0.8: 2257 | dev: true 2258 | resolution: 2259 | integrity: sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ= 2260 | tarball: 'http://registry.npm.taobao.org/rx-lite/download/rx-lite-4.0.8.tgz' 2261 | /safe-buffer/5.1.1: 2262 | resolution: 2263 | integrity: sha1-iTMSr2myEj3vcfV4iQAWce6yyFM= 2264 | tarball: 'http://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.1.tgz' 2265 | /safer-buffer/2.1.2: 2266 | dev: false 2267 | resolution: 2268 | integrity: sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo= 2269 | tarball: 'http://registry.npm.taobao.org/safer-buffer/download/safer-buffer-2.1.2.tgz' 2270 | /secure-keys/1.0.0: 2271 | dev: false 2272 | resolution: 2273 | integrity: sha1-8MgtmKOxOah3aogIBQuCRDEIf8o= 2274 | tarball: 'http://registry.npm.taobao.org/secure-keys/download/secure-keys-1.0.0.tgz' 2275 | /semver/5.3.0: 2276 | dev: true 2277 | resolution: 2278 | integrity: sha1-myzl094C0XxgEq0yaqa00M9U+U8= 2279 | tarball: 'http://registry.npm.taobao.org/semver/download/semver-5.3.0.tgz' 2280 | /semver/5.5.0: 2281 | resolution: 2282 | integrity: sha1-3Eu8emyp2Rbe5dQ1FvAJK1j3uKs= 2283 | tarball: 'http://registry.npm.taobao.org/semver/download/semver-5.5.0.tgz' 2284 | /seq-queue/0.0.5: 2285 | dev: false 2286 | resolution: 2287 | integrity: sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4= 2288 | tarball: 'http://registry.npm.taobao.org/seq-queue/download/seq-queue-0.0.5.tgz' 2289 | /sequelize/4.33.4: 2290 | dependencies: 2291 | bluebird: 3.5.1 2292 | cls-bluebird: 2.1.0 2293 | debug: 3.1.0 2294 | depd: 1.1.2 2295 | dottie: 2.0.0 2296 | generic-pool: 3.4.2 2297 | inflection: 1.12.0 2298 | lodash: 4.17.5 2299 | moment: 2.20.1 2300 | moment-timezone: 0.5.14 2301 | retry-as-promised: 2.3.2 2302 | semver: 5.5.0 2303 | terraformer-wkt-parser: 1.1.2 2304 | toposort-class: 1.0.1 2305 | uuid: 3.2.1 2306 | validator: 9.4.1 2307 | wkx: 0.4.2 2308 | dev: false 2309 | engines: 2310 | node: '>=4.0.0' 2311 | resolution: 2312 | integrity: sha1-VYEYPou5Akmeob6WqoWFdDormxw= 2313 | tarball: 'http://registry.npm.taobao.org/sequelize/download/sequelize-4.33.4.tgz' 2314 | /setprototypeof/1.0.3: 2315 | resolution: 2316 | integrity: sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ= 2317 | tarball: 'http://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.0.3.tgz' 2318 | /shebang-command/1.2.0: 2319 | dependencies: 2320 | shebang-regex: 1.0.0 2321 | dev: true 2322 | engines: 2323 | node: '>=0.10.0' 2324 | resolution: 2325 | integrity: sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= 2326 | tarball: 'http://registry.npm.taobao.org/shebang-command/download/shebang-command-1.2.0.tgz' 2327 | /shebang-regex/1.0.0: 2328 | dev: true 2329 | engines: 2330 | node: '>=0.10.0' 2331 | resolution: 2332 | integrity: sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= 2333 | tarball: 'http://registry.npm.taobao.org/shebang-regex/download/shebang-regex-1.0.0.tgz' 2334 | /shimmer/1.2.0: 2335 | dev: false 2336 | resolution: 2337 | integrity: sha1-+Wb3VVeJdj502IQRk2haXnhzZmU= 2338 | tarball: 'http://registry.npm.taobao.org/shimmer/download/shimmer-1.2.0.tgz' 2339 | /signal-exit/3.0.2: 2340 | dev: true 2341 | resolution: 2342 | integrity: sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= 2343 | tarball: 'http://registry.npm.taobao.org/signal-exit/download/signal-exit-3.0.2.tgz' 2344 | /simple-netease-cloud-music/0.3.4: 2345 | dev: false 2346 | resolution: 2347 | integrity: sha1-9LwYFjCdIEim81CeAM2qC0bu2cE= 2348 | tarball: 'http://registry.npm.taobao.org/simple-netease-cloud-music/download/simple-netease-cloud-music-0.3.4.tgz' 2349 | /slice-ansi/1.0.0: 2350 | dependencies: 2351 | is-fullwidth-code-point: 2.0.0 2352 | dev: true 2353 | engines: 2354 | node: '>=4' 2355 | resolution: 2356 | integrity: sha1-BE8aSdiEL/MHqta1Be0Xi9lQE00= 2357 | tarball: 'http://registry.npm.taobao.org/slice-ansi/download/slice-ansi-1.0.0.tgz' 2358 | /spdx-correct/1.0.2: 2359 | dependencies: 2360 | spdx-license-ids: 1.2.2 2361 | dev: true 2362 | resolution: 2363 | integrity: sha1-SzBz2TP/UfORLwOsVRlJikFQ20A= 2364 | tarball: 'http://registry.npm.taobao.org/spdx-correct/download/spdx-correct-1.0.2.tgz' 2365 | /spdx-expression-parse/1.0.4: 2366 | dev: true 2367 | resolution: 2368 | integrity: sha1-m98vIOH0DtRH++JzJmGR/O1RYmw= 2369 | tarball: 'http://registry.npm.taobao.org/spdx-expression-parse/download/spdx-expression-parse-1.0.4.tgz' 2370 | /spdx-license-ids/1.2.2: 2371 | dev: true 2372 | resolution: 2373 | integrity: sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc= 2374 | tarball: 'http://registry.npm.taobao.org/spdx-license-ids/download/spdx-license-ids-1.2.2.tgz' 2375 | /sprintf-js/1.0.3: 2376 | dev: true 2377 | resolution: 2378 | integrity: sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= 2379 | tarball: 'http://registry.npm.taobao.org/sprintf-js/download/sprintf-js-1.0.3.tgz' 2380 | /sqlstring/2.3.0: 2381 | dev: false 2382 | engines: 2383 | node: '>= 0.6' 2384 | resolution: 2385 | integrity: sha1-UluKT9Jtb3GqYegipsr5dtMa0qg= 2386 | tarball: 'http://registry.npm.taobao.org/sqlstring/download/sqlstring-2.3.0.tgz' 2387 | /stack-trace/0.0.10: 2388 | dev: false 2389 | resolution: 2390 | integrity: sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= 2391 | tarball: 'http://registry.npm.taobao.org/stack-trace/download/stack-trace-0.0.10.tgz' 2392 | /statuses/1.4.0: 2393 | engines: 2394 | node: '>= 0.6' 2395 | resolution: 2396 | integrity: sha1-u3PURtonlhBu/MG2AaJT1sRr0Ic= 2397 | tarball: 'http://registry.npm.taobao.org/statuses/download/statuses-1.4.0.tgz' 2398 | /stream-injecter/0.0.1: 2399 | dev: true 2400 | resolution: 2401 | integrity: sha1-dQeuXquYNlNmZega2M4xtA/j1FU= 2402 | tarball: 'http://registry.npm.taobao.org/stream-injecter/download/stream-injecter-0.0.1.tgz' 2403 | /streaming-json-stringify/3.1.0: 2404 | dependencies: 2405 | json-stringify-safe: 5.0.1 2406 | readable-stream: 2.3.4 2407 | dev: false 2408 | resolution: 2409 | integrity: sha1-gCAEN6mTzDnE/gAmO3s7kDrIevU= 2410 | tarball: 'http://registry.npm.taobao.org/streaming-json-stringify/download/streaming-json-stringify-3.1.0.tgz' 2411 | /string-width/1.0.2: 2412 | dependencies: 2413 | code-point-at: 1.1.0 2414 | is-fullwidth-code-point: 1.0.0 2415 | strip-ansi: 3.0.1 2416 | dev: false 2417 | engines: 2418 | node: '>=0.10.0' 2419 | resolution: 2420 | integrity: sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= 2421 | tarball: 'http://registry.npm.taobao.org/string-width/download/string-width-1.0.2.tgz' 2422 | /string-width/2.1.1: 2423 | dependencies: 2424 | is-fullwidth-code-point: 2.0.0 2425 | strip-ansi: 4.0.0 2426 | dev: true 2427 | engines: 2428 | node: '>=4' 2429 | resolution: 2430 | integrity: sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4= 2431 | tarball: 'http://registry.npm.taobao.org/string-width/download/string-width-2.1.1.tgz' 2432 | /string_decoder/1.0.3: 2433 | dependencies: 2434 | safe-buffer: 5.1.1 2435 | resolution: 2436 | integrity: sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs= 2437 | tarball: 'http://registry.npm.taobao.org/string_decoder/download/string_decoder-1.0.3.tgz' 2438 | /strip-ansi/3.0.1: 2439 | dependencies: 2440 | ansi-regex: 2.1.1 2441 | engines: 2442 | node: '>=0.10.0' 2443 | resolution: 2444 | integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= 2445 | tarball: 'http://registry.npm.taobao.org/strip-ansi/download/strip-ansi-3.0.1.tgz' 2446 | /strip-ansi/4.0.0: 2447 | dependencies: 2448 | ansi-regex: 3.0.0 2449 | dev: true 2450 | engines: 2451 | node: '>=4' 2452 | resolution: 2453 | integrity: sha1-qEeQIusaw2iocTibY1JixQXuNo8= 2454 | tarball: 'http://registry.npm.taobao.org/strip-ansi/download/strip-ansi-4.0.0.tgz' 2455 | /strip-bom/3.0.0: 2456 | dev: true 2457 | engines: 2458 | node: '>=4' 2459 | resolution: 2460 | integrity: sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= 2461 | tarball: 'http://registry.npm.taobao.org/strip-bom/download/strip-bom-3.0.0.tgz' 2462 | /strip-json-comments/2.0.1: 2463 | dev: true 2464 | engines: 2465 | node: '>=0.10.0' 2466 | resolution: 2467 | integrity: sha1-PFMZQukIwml8DsNEhYwobHygpgo= 2468 | tarball: 'http://registry.npm.taobao.org/strip-json-comments/download/strip-json-comments-2.0.1.tgz' 2469 | /supervisor/0.12.0: 2470 | dev: true 2471 | engines: 2472 | node: '>=0.6.0' 2473 | resolution: 2474 | integrity: sha1-3n5jNwFbKRhRwQ81OMSn8EkX7ME= 2475 | tarball: 'http://registry.npm.taobao.org/supervisor/download/supervisor-0.12.0.tgz' 2476 | /supports-color/2.0.0: 2477 | dev: true 2478 | engines: 2479 | node: '>=0.8.0' 2480 | resolution: 2481 | integrity: sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= 2482 | tarball: 'http://registry.npm.taobao.org/supports-color/download/supports-color-2.0.0.tgz' 2483 | /supports-color/4.4.0: 2484 | dependencies: 2485 | has-flag: 2.0.0 2486 | dev: true 2487 | engines: 2488 | node: '>=4' 2489 | resolution: 2490 | integrity: sha1-iD992rwWUUKyphQn8zUt7RldGj4= 2491 | tarball: 'http://registry.npm.taobao.org/supports-color/download/supports-color-4.4.0.tgz' 2492 | /supports-color/5.2.0: 2493 | dependencies: 2494 | has-flag: 3.0.0 2495 | engines: 2496 | node: '>=4' 2497 | resolution: 2498 | integrity: sha1-sNUzOxGE3TZmy+WqC0XFrHrBeko= 2499 | tarball: 'http://registry.npm.taobao.org/supports-color/download/supports-color-5.2.0.tgz' 2500 | /table/4.0.2: 2501 | dependencies: 2502 | ajv: 5.5.2 2503 | ajv-keywords: /ajv-keywords/2.1.1/ajv@5.5.2 2504 | chalk: 2.3.1 2505 | lodash: 4.17.5 2506 | slice-ansi: 1.0.0 2507 | string-width: 2.1.1 2508 | dev: true 2509 | resolution: 2510 | integrity: sha1-ozRHN1OR52atNNNIbm4q7chNLjY= 2511 | tarball: 'http://registry.npm.taobao.org/table/download/table-4.0.2.tgz' 2512 | /terraformer-wkt-parser/1.1.2: 2513 | dependencies: 2514 | terraformer: 1.0.8 2515 | dev: false 2516 | resolution: 2517 | integrity: sha1-M2oMj8gglKWv+DKI9prt7NNpvww= 2518 | tarball: 'http://registry.npm.taobao.org/terraformer-wkt-parser/download/terraformer-wkt-parser-1.1.2.tgz' 2519 | /terraformer/1.0.8: 2520 | dependencies: 2521 | '@types/geojson': 1.0.6 2522 | dev: false 2523 | engines: 2524 | node: '>=4.2.6' 2525 | resolution: 2526 | integrity: sha1-UeCtiXRvzyFh3G9lqnDkI3fItZM= 2527 | tarball: 'http://registry.npm.taobao.org/terraformer/download/terraformer-1.0.8.tgz' 2528 | /text-table/0.2.0: 2529 | dev: true 2530 | resolution: 2531 | integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= 2532 | tarball: 'http://registry.npm.taobao.org/text-table/download/text-table-0.2.0.tgz' 2533 | /thenify-all/1.6.0: 2534 | dependencies: 2535 | thenify: 3.3.0 2536 | dev: false 2537 | engines: 2538 | node: '>=0.8' 2539 | resolution: 2540 | integrity: sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY= 2541 | tarball: 'http://registry.npm.taobao.org/thenify-all/download/thenify-all-1.6.0.tgz' 2542 | /thenify/3.3.0: 2543 | dependencies: 2544 | any-promise: 1.3.0 2545 | dev: false 2546 | resolution: 2547 | integrity: sha1-5p44obq+lpsBCCB5eLn2K4hgSDk= 2548 | tarball: 'http://registry.npm.taobao.org/thenify/download/thenify-3.3.0.tgz' 2549 | /through/2.3.8: 2550 | dev: true 2551 | resolution: 2552 | integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= 2553 | tarball: 'http://registry.npm.taobao.org/through/download/through-2.3.8.tgz' 2554 | /tmp/0.0.33: 2555 | dependencies: 2556 | os-tmpdir: 1.0.2 2557 | dev: true 2558 | engines: 2559 | node: '>=0.6.0' 2560 | resolution: 2561 | integrity: sha1-bTQzWIl2jSGyvNoKonfO07G/rfk= 2562 | tarball: 'http://registry.npm.taobao.org/tmp/download/tmp-0.0.33.tgz' 2563 | /toposort-class/1.0.1: 2564 | dev: false 2565 | resolution: 2566 | integrity: sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg= 2567 | tarball: 'http://registry.npm.taobao.org/toposort-class/download/toposort-class-1.0.1.tgz' 2568 | /type-check/0.3.2: 2569 | dependencies: 2570 | prelude-ls: 1.1.2 2571 | dev: true 2572 | engines: 2573 | node: '>= 0.8.0' 2574 | resolution: 2575 | integrity: sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= 2576 | tarball: 'http://registry.npm.taobao.org/type-check/download/type-check-0.3.2.tgz' 2577 | /type-detect/4.0.8: 2578 | dev: true 2579 | engines: 2580 | node: '>=4' 2581 | resolution: 2582 | integrity: sha1-dkb7XxiHHPu3dJ5pvTmmOI63RQw= 2583 | tarball: 'http://registry.npm.taobao.org/type-detect/download/type-detect-4.0.8.tgz' 2584 | /type-is/1.6.16: 2585 | dependencies: 2586 | media-typer: 0.3.0 2587 | mime-types: 2.1.18 2588 | engines: 2589 | node: '>= 0.6' 2590 | resolution: 2591 | integrity: sha1-+JzjQVQcZysl7nrjxz3uOyvlAZQ= 2592 | tarball: 'http://registry.npm.taobao.org/type-is/download/type-is-1.6.16.tgz' 2593 | /typedarray/0.0.6: 2594 | dev: true 2595 | resolution: 2596 | integrity: sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= 2597 | tarball: 'http://registry.npm.taobao.org/typedarray/download/typedarray-0.0.6.tgz' 2598 | /unpipe/1.0.0: 2599 | dev: false 2600 | engines: 2601 | node: '>= 0.8' 2602 | resolution: 2603 | integrity: sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= 2604 | tarball: 'http://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz' 2605 | /urijs/1.19.1: 2606 | dev: false 2607 | resolution: 2608 | integrity: sha1-Ww/1MMDL3oOG9jQiNbpcpumV0lo= 2609 | tarball: 'http://registry.npm.taobao.org/urijs/download/urijs-1.19.1.tgz' 2610 | /util-deprecate/1.0.2: 2611 | resolution: 2612 | integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= 2613 | tarball: 'http://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz' 2614 | /uuid/3.2.1: 2615 | dev: false 2616 | resolution: 2617 | integrity: sha1-EsUou51Y0LkmXZovbw/ovhf/HxQ= 2618 | tarball: 'http://registry.npm.taobao.org/uuid/download/uuid-3.2.1.tgz' 2619 | /validate-npm-package-license/3.0.1: 2620 | dependencies: 2621 | spdx-correct: 1.0.2 2622 | spdx-expression-parse: 1.0.4 2623 | dev: true 2624 | resolution: 2625 | integrity: sha1-KAS6vnEq0zeUWaz74kdGqywwP7w= 2626 | tarball: 'http://registry.npm.taobao.org/validate-npm-package-license/download/validate-npm-package-license-3.0.1.tgz' 2627 | /validator/9.4.1: 2628 | dev: false 2629 | engines: 2630 | node: '>= 0.10' 2631 | resolution: 2632 | integrity: sha1-q/Rm05i1Yc0kMFARLG/x3mzBJmM= 2633 | tarball: 'http://registry.npm.taobao.org/validator/download/validator-9.4.1.tgz' 2634 | /vary/1.1.2: 2635 | engines: 2636 | node: '>= 0.8' 2637 | resolution: 2638 | integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= 2639 | tarball: 'http://registry.npm.taobao.org/vary/download/vary-1.1.2.tgz' 2640 | /which/1.3.0: 2641 | dependencies: 2642 | isexe: 2.0.0 2643 | dev: true 2644 | resolution: 2645 | integrity: sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo= 2646 | tarball: 'http://registry.npm.taobao.org/which/download/which-1.3.0.tgz' 2647 | /window-size/0.1.4: 2648 | dev: false 2649 | engines: 2650 | node: '>= 0.10.0' 2651 | resolution: 2652 | integrity: sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY= 2653 | tarball: 'http://registry.npm.taobao.org/window-size/download/window-size-0.1.4.tgz' 2654 | /winston/2.4.0: 2655 | dependencies: 2656 | async: 1.0.0 2657 | colors: 1.0.3 2658 | cycle: 1.0.3 2659 | eyes: 0.1.8 2660 | isstream: 0.1.2 2661 | stack-trace: 0.0.10 2662 | dev: false 2663 | engines: 2664 | node: '>= 0.10.0' 2665 | resolution: 2666 | integrity: sha1-gIBQuT1SZh7Z+2wms/DIJnCLCu4= 2667 | tarball: 'http://registry.npm.taobao.org/winston/download/winston-2.4.0.tgz' 2668 | /wkx/0.4.2: 2669 | dependencies: 2670 | '@types/node': 9.4.6 2671 | dev: false 2672 | resolution: 2673 | integrity: sha1-d201pjSlwi5lbkdEvetU+D/Szo0= 2674 | tarball: 'http://registry.npm.taobao.org/wkx/download/wkx-0.4.2.tgz' 2675 | /wordwrap/1.0.0: 2676 | dev: true 2677 | resolution: 2678 | integrity: sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= 2679 | tarball: 'http://registry.npm.taobao.org/wordwrap/download/wordwrap-1.0.0.tgz' 2680 | /wrap-ansi/2.1.0: 2681 | dependencies: 2682 | string-width: 1.0.2 2683 | strip-ansi: 3.0.1 2684 | dev: false 2685 | engines: 2686 | node: '>=0.10.0' 2687 | resolution: 2688 | integrity: sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= 2689 | tarball: 'http://registry.npm.taobao.org/wrap-ansi/download/wrap-ansi-2.1.0.tgz' 2690 | /wrappy/1.0.2: 2691 | dev: true 2692 | resolution: 2693 | integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 2694 | tarball: 'http://registry.npm.taobao.org/wrappy/download/wrappy-1.0.2.tgz' 2695 | /write/0.2.1: 2696 | dependencies: 2697 | mkdirp: 0.5.1 2698 | dev: true 2699 | engines: 2700 | node: '>=0.10.0' 2701 | resolution: 2702 | integrity: sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c= 2703 | tarball: 'http://registry.npm.taobao.org/write/download/write-0.2.1.tgz' 2704 | /x-xss-protection/1.0.0: 2705 | dev: false 2706 | resolution: 2707 | integrity: sha1-iYr7k4abJGYc+cUvnujbjtB2Tdk= 2708 | tarball: 'http://registry.npm.taobao.org/x-xss-protection/download/x-xss-protection-1.0.0.tgz' 2709 | /y18n/3.2.1: 2710 | dev: false 2711 | resolution: 2712 | integrity: sha1-bRX7qITAhnnA136I53WegR4H+kE= 2713 | tarball: 'http://registry.npm.taobao.org/y18n/download/y18n-3.2.1.tgz' 2714 | /yallist/2.1.2: 2715 | resolution: 2716 | integrity: sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= 2717 | tarball: 'http://registry.npm.taobao.org/yallist/download/yallist-2.1.2.tgz' 2718 | /yargs/3.32.0: 2719 | dependencies: 2720 | camelcase: 2.1.1 2721 | cliui: 3.2.0 2722 | decamelize: 1.2.0 2723 | os-locale: 1.4.0 2724 | string-width: 1.0.2 2725 | window-size: 0.1.4 2726 | y18n: 3.2.1 2727 | dev: false 2728 | resolution: 2729 | integrity: sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU= 2730 | tarball: 'http://registry.npm.taobao.org/yargs/download/yargs-3.32.0.tgz' 2731 | registry: 'https://registry.npm.taobao.org/' 2732 | shrinkwrapMinorVersion: 5 2733 | shrinkwrapVersion: 3 2734 | specifiers: 2735 | async: ^2.6.0 2736 | axios: ^0.17.1 2737 | bluebird: ^3.5.1 2738 | bytes: ^3.0.0 2739 | chai: ^4.1.2 2740 | chalk: ^2.3.0 2741 | colors: ^1.1.2 2742 | cron: ^1.3.0 2743 | eslint: ^4.16.0 2744 | eslint-config-standard: ^11.0.0-beta.0 2745 | eslint-plugin-import: ^2.8.0 2746 | eslint-plugin-node: ^5.2.1 2747 | eslint-plugin-promise: ^3.6.0 2748 | eslint-plugin-standard: ^3.0.1 2749 | humanize-number: ^0.0.2 2750 | iconv-lite: ^0.4.19 2751 | kcors: ^2.2.1 2752 | koa: ^2.4.1 2753 | koa-bodyparser: ^4.2.0 2754 | koa-compress: ^2.0.0 2755 | koa-favicon: ^2.0.0 2756 | koa-helmet: ^3.3.0 2757 | koa-json: ^2.0.2 2758 | koa-jsonp: ^2.0.2 2759 | koa-livereload: ^0.2.0 2760 | koa-query-pretty: ^0.3.0 2761 | koa-router: ^7.3.0 2762 | lodash: ^4.17.4 2763 | lodash.compact: ^3.0.1 2764 | lodash.curry: ^4.1.1 2765 | mocha: ^5.0.0 2766 | mysql2: ^1.5.1 2767 | nconf: ^0.10.0 2768 | netease-music-sdk: ^0.3.1 2769 | nodemailer: ^4.4.2 2770 | nyc: ^11.4.1 2771 | passthrough-counter: ^1.0.0 2772 | pify: ^3.0.0 2773 | redis: ^2.8.0 2774 | sequelize: ^4.32.2 2775 | simple-netease-cloud-music: ^0.3.4 2776 | supervisor: ^0.12.0 2777 | uuid: ^3.2.1 2778 | winston: ^2.4.0 2779 | -------------------------------------------------------------------------------- /src/cache.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Import Packages 3 | const nconf = require('nconf') 4 | const bluebrid = require('bluebird') 5 | const winston = require('winston') 6 | // Promisify Redis 7 | const redis = bluebrid.promisifyAll(require('redis')) 8 | 9 | class cache { 10 | static connect () { 11 | if (this.hasOwnProperty('redis')) { 12 | return true 13 | } else { 14 | // Get Config 15 | const config = { 16 | host: nconf.get('redis:host') || '127.0.0.1', 17 | port: nconf.get('redis:port') || 6379, 18 | password: nconf.get('redis:password') && nconf.get('redis:password') !== '' ? nconf.get('redis:password') : false, 19 | db: nconf.get('redis:database') || 0 20 | } 21 | if (!config.password) { 22 | delete config.password 23 | } 24 | // Connect Redis 25 | this.redis = redis.createClient(config) 26 | this.redis.on('error', err => { 27 | winston.error(err) 28 | process.exit(1) 29 | }) 30 | return true 31 | } 32 | } 33 | static command (commands, params) { 34 | this.connect() 35 | const param = params 36 | param[0] = 'cache:' + param[0] 37 | return this.redis[commands + 'Async'](param) 38 | } 39 | 40 | static set (key, v, time) { 41 | this.connect() 42 | const value = typeof v === 'object' ? JSON.stringify(v) : v 43 | if (time) { 44 | return this.redis.setAsync('cache:' + key, value, 'EX', time) 45 | } else { 46 | return this.redis.setAsync('cache:' + key, value) 47 | } 48 | } 49 | 50 | static async get (key, toJson = true) { 51 | this.connect() 52 | const data = await this.redis.getAsync('cache:' + key) 53 | if (toJson) { 54 | try { 55 | const json = JSON.parse(data) 56 | return json 57 | } catch (e) { 58 | return data 59 | } 60 | } else { 61 | return data 62 | } 63 | } 64 | } 65 | 66 | module.exports = cache 67 | -------------------------------------------------------------------------------- /src/controller.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Load Packages 3 | const fs = require('fs') 4 | const winston = require('winston') 5 | const colors = require('colors/safe') 6 | const path = require('path') 7 | 8 | class controllers { 9 | constructor (controller) { 10 | if (controller) { 11 | // Register Controller 12 | return this.register(controller) 13 | } else { 14 | // Auto-load All Controllers 15 | return this.load() 16 | } 17 | } 18 | 19 | register (controller) { 20 | try { 21 | return require(path.join(__dirname, '../', './src/controllers', controller)) 22 | } catch (e) { 23 | winston.error(colors.red(e)) 24 | // mail.error(e) 25 | process.exit(1) 26 | } 27 | } 28 | 29 | async load () { 30 | try { 31 | // Load Controller 32 | let controllers = {} 33 | const dir = fs.readdirSync(path.join(__dirname, '../', './src/controllers')) 34 | await dir.map((item, index, input) => { 35 | controllers[item.substring(0, item.length - 3)] = module.parent.require(path.join(__dirname, '../', './src/controllers/' + item)) 36 | }) 37 | return controllers 38 | } catch (e) { 39 | winston.error(colors.red(e)) 40 | process.exit(1) 41 | } 42 | } 43 | } 44 | 45 | module.exports = controllers 46 | -------------------------------------------------------------------------------- /src/controllers/hello.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | index: async (ctx, next) => { 4 | ctx.body = { 5 | message: 'Test Controller', 6 | ts: Date.now() 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/controllers/netease.js: -------------------------------------------------------------------------------- 1 | // Import Packages 2 | const NeteaseMusic = require('simple-netease-cloud-music') 3 | const async = require('async') 4 | const { MusicClient } = require('netease-music-sdk') 5 | const pify = require('pify') 6 | const cache = require('../cache') 7 | const nconf = require('nconf') 8 | const nm = new NeteaseMusic() 9 | const sdk = new MusicClient() 10 | const controllers = {} 11 | 12 | // get mv data 13 | controllers.mv = async (ctx, next) => { 14 | let mvid 15 | try { 16 | mvid = Number.parseInt(ctx.params.mvid) 17 | } catch (e) { 18 | ctx.body = { 19 | status: 400, 20 | message: '参数必须为数字', 21 | ts: Date.now() 22 | } 23 | return 24 | } 25 | let result = await cache.get(`nm:mv:${mvid}`, false) 26 | if (result) { 27 | ctx.set('Content-Type', 'application/json') 28 | ctx.body = result 29 | return 30 | } 31 | try { 32 | result = await sdk.getMvInfo(mvid) 33 | cache.set(`nm:mv:${mvid}`, result, 60 * 60 * 2) 34 | ctx.body = result 35 | } catch (e) { 36 | ctx.status = 404 37 | ctx.body = { 38 | status: 404 39 | } 40 | } 41 | } 42 | 43 | // Get DJ Program Info 44 | controllers.djProgramInfo = async (ctx, next) => { 45 | let pid 46 | try { 47 | pid = Number.parseInt(ctx.params.pid) 48 | } catch (e) { 49 | ctx.body = { 50 | code: 400, 51 | message: '参数必须为数字', 52 | ts: Date.now() 53 | } 54 | return 55 | } 56 | let result = await cache.get(`nm:dj:program:info:${pid}`, false) 57 | if (result) { 58 | ctx.set('Content-Type', 'application/json') 59 | ctx.body = result 60 | return 61 | } 62 | result = await sdk.getRadioProgramInfo(pid) 63 | if (result && result.code === 200) { 64 | cache.set(`nm:dj:program:info:${pid}`, result, 60 * 60 * 2) 65 | } 66 | ctx.body = result 67 | } 68 | 69 | // Get DJ detail 70 | controllers.djDetail = async (ctx, next) => { 71 | let rid 72 | try { 73 | rid = Number.parseInt(ctx.params.rid) 74 | } catch (e) { 75 | ctx.body = { 76 | code: 400, 77 | message: '参数必须为数字', 78 | ts: Date.now() 79 | } 80 | return 81 | } 82 | let result = await cache.get(`nm:dj:info:${rid}`, false) 83 | if (result) { 84 | ctx.set('Content-Type', 'application/json') 85 | ctx.body = result 86 | return 87 | } 88 | result = await sdk.getRadioInfo(rid) 89 | if (result && result.code === 200) { 90 | cache.set(`nm:dj:info:${rid}`, result, 60 * 60 * 2) 91 | } 92 | ctx.body = result 93 | } 94 | 95 | // Get DJ Program 96 | controllers.djProgram = async (ctx, next) => { 97 | let rid 98 | let limit 99 | let offset 100 | try { 101 | rid = Number.parseInt(ctx.params.rid) 102 | limit = ctx.query && ctx.query.limit && typeof ctx.query.limit === 'number' ? ctx.query.limit : 30 103 | offset = ctx.query && ctx.query.offset && typeof ctx.query.limit === 'number' ? ctx.query.limit : 0 104 | } catch (e) { 105 | ctx.body = { 106 | code: 400, 107 | message: '参数必须为数字', 108 | ts: Date.now() 109 | } 110 | return 111 | } 112 | const asc = !!(ctx.query && ctx.query.asc) 113 | let result = await cache.get(`nm:dj:program:${rid}`, false) 114 | if (result) { 115 | ctx.set('Content-Type', 'application/json') 116 | ctx.body = result 117 | return 118 | } 119 | result = await sdk.getRadioProgram(rid, asc, limit, offset) 120 | if (result && result.code === 200) { 121 | cache.set(`nm:dj:program:${rid}`, result, 60 * 60 * 2) 122 | } 123 | ctx.body = result 124 | } 125 | 126 | // Get User DJ 127 | controllers.userDj = async (ctx, next) => { 128 | let uid 129 | let offset 130 | let limit 131 | try { 132 | uid = Number.parseInt(ctx.params.uid) 133 | limit = ctx.query && ctx.query.limit && typeof ctx.query.limit === 'number' ? ctx.query.limit : 30 134 | offset = ctx.query && ctx.query.offset && typeof ctx.query.limit === 'number' ? ctx.query.limit : 0 135 | } catch (e) { 136 | ctx.body = { 137 | code: 400, 138 | message: '参数必须为数字', 139 | ts: Date.now() 140 | } 141 | return 142 | } 143 | let result = await cache.get(`nm:user:dj:${uid}`, false) 144 | if (result) { 145 | ctx.set('Content-Type', 'application/json') 146 | ctx.body = result 147 | return 148 | } 149 | result = await sdk.getUserDj(uid, limit, offset) 150 | if (result.code && result.code === 200) { 151 | cache.set(`nm:user:dj:${uid}`, result, 60 * 60 * 2) 152 | } 153 | ctx.body = result 154 | } 155 | 156 | // Get Music Comment 157 | controllers.musicComment = async (ctx, next) => { 158 | let id 159 | let offset 160 | let limit 161 | try { 162 | id = Number.parseInt(ctx.params.id) 163 | limit = ctx.query && ctx.query.limit && typeof ctx.query.limit === 'number' ? ctx.query.limit : 30 164 | offset = ctx.query && ctx.query.offset && typeof ctx.query.limit === 'number' ? ctx.query.limit : 0 165 | } catch (e) { 166 | ctx.body = { 167 | code: 400, 168 | message: '参数必须为数字', 169 | ts: Date.now() 170 | } 171 | return 172 | } 173 | let result = await cache.get(`nm:music:comment:${id}:${limit}:${offset}`, false) 174 | if (result) { 175 | ctx.set('Content-Type', 'application/json') 176 | ctx.body = result 177 | return 178 | } 179 | result = await sdk.getSongComment(id, limit, offset) 180 | if (result.code && result.code === 200) { 181 | cache.set(`nm:music:comment:${id}:${limit}:${offset}`, result, 60 * 60 * 2) // 2 Hour 182 | } 183 | ctx.body = result 184 | } 185 | 186 | // Get Music record 187 | controllers.record = async (ctx, next) => { 188 | let uid 189 | try { 190 | uid = Number.parseInt(ctx.params.uid) 191 | } catch (e) { 192 | ctx.code = 400 193 | ctx.body = { 194 | status: 400, 195 | message: 'uid 必须为数字', 196 | ts: Date.now() 197 | } 198 | return 199 | } 200 | const type = ctx.query && ctx.query.weekly ? 1 : 0 201 | let result = await cache.get(`nm:user:record:${uid}:${type}`, false) 202 | if (result) { 203 | ctx.set('Content-Type', 'application/json') 204 | ctx.body = result 205 | return 206 | } 207 | result = await sdk.getUserRecord(uid, type) 208 | if (result.code && result.code === 200) { 209 | cache.set(`nm:user:record:${uid}:${type}`, result, 60 * 60 * 2) 210 | } 211 | ctx.body = result 212 | } 213 | 214 | // Get Music Summary 215 | controllers.summary = async (ctx, next) => { 216 | // Remove End ',' 217 | const id = ctx.params.id && ctx.params.id.endsWith(',') ? ctx.params.id.slice(0, ctx.params.id.length - 1) : ctx.params.id 218 | // Limit 100 ids 219 | if (id.match(',') && id.split(',').length > 100) { 220 | Response400(ctx, 1) 221 | return 222 | } 223 | if (ctx.query.quick) { 224 | // Without Check song whether is blocked 225 | const ret = await quickSummary(id, ctx) 226 | if (ret) { 227 | const data = ctx.query && ctx.query.common ? handleResult(ret, true) : handleResult(ret) 228 | data.code = 200 229 | data.message = 'ok' 230 | ctx.body = data 231 | } else { 232 | Response400(ctx) 233 | } 234 | } else { 235 | const ret = await silentSummary(id, ctx) 236 | if (ret) { 237 | const data = ctx.query && ctx.query.common ? handleResult(ret, true) : handleResult(ret) 238 | data.code = 200 239 | data.message = 'ok' 240 | ctx.body = data 241 | } else { 242 | Response400(ctx) 243 | } 244 | } 245 | } 246 | 247 | // Redirect Music URL 248 | controllers.redirect = async (ctx, next) => { 249 | const ret = await nm.url(ctx.params.id) 250 | if (ret && ret.data && ret.data[0].url) { 251 | ctx.redirect(ret.data[0].url.replace(/http:\/\/m(\d+)[a-zA-Z]*/, 'https://m$1')) 252 | } else { 253 | Response400(ctx) 254 | } 255 | } 256 | 257 | const getType = ctx => { 258 | if (!(ctx.query && ctx.query.type)) { 259 | return 1 260 | } 261 | const type = ctx.query.type.toLocaleUpperCase() 262 | switch (type) { 263 | case 'ALBUM': 264 | return 10 265 | case 'ARTIST': 266 | return 100 267 | case 'DJ': 268 | return 1009 269 | case 'LYRIC': 270 | return 1006 271 | case 'MV': 272 | return 1004 273 | case 'PLAYLIST': 274 | return 1000 275 | case 'SONG': 276 | return 1 277 | case 'USER': 278 | return 1002 279 | default: 280 | return 1 281 | } 282 | } 283 | 284 | // Search API 285 | controllers.search = async (ctx, next) => { 286 | const keyword = ctx.params.keyword 287 | const limit = ctx.query && ctx.query.limit && typeof ctx.query.limit === 'number' ? ctx.query.limit : 30 288 | const offset = ctx.query && ctx.query.offset && typeof ctx.query.limit === 'number' ? ctx.query.limit : 0 289 | const type = getType(ctx) 290 | let ret = await cache.get(`nm:search:${keyword}:${limit}:${offset}:${type}`) 291 | if (ret) { 292 | ctx.set('Content-Type', 'application/json') 293 | ctx.body = ret 294 | return 295 | } 296 | ret = await sdk.search(keyword, type, limit, offset) 297 | cache.set(`nm:search:${keyword}:${limit}:${offset}:${type}`, ret, 60 * 60 * 2) // Cache 2 Hour 298 | ctx.body = ret || { 299 | code: 400, 300 | message: 'API 在请求时出现了问题,再试一下看看?', 301 | feedback: '访问 /status 获得详细信息', 302 | now: new Date().toString() 303 | } 304 | } 305 | 306 | // Playlist API 307 | controllers.playlist = async (ctx, next) => { 308 | let ret 309 | let Cache = await cache.get('nm:playlist:' + ctx.params.id, false) 310 | if (Cache) { 311 | ctx.set('Content-Type', 'application/json') 312 | ctx.body = Cache 313 | return 314 | } else { 315 | ret = await nm.playlist(ctx.params.id) 316 | if (ret.code !== 404) { 317 | cache.set('nm:playlist:' + ctx.params.id, ret, 60 * 60 * 2) // Cache 2 Hour 318 | } 319 | } 320 | ctx.body = ret || { 321 | code: 400, 322 | message: 'API 在请求时出现了问题,再试一下看看?', 323 | feedback: '访问 /status 获得详细信息', 324 | now: new Date().toString() 325 | } 326 | } 327 | 328 | // Picture API 329 | controllers.picture = async (ctx, next) => { 330 | const ret = ctx.params.height ? await nm.picture(ctx.params.id, ctx.params.height) : await nm.picture(ctx.params.id) 331 | ctx.body = ret || { 332 | code: 400, 333 | message: 'API 在请求时出现了问题,再试一下看看?', 334 | feedback: '访问 /status 获得详细信息', 335 | now: new Date().toString() 336 | } 337 | } 338 | 339 | // Artist API 340 | controllers.artist = async (ctx, next) => { 341 | let ret 342 | let Cache = await cache.get('nm:artist:' + ctx.params.id, false) 343 | if (Cache) { 344 | ctx.set('Content-Type', 'application/json') 345 | ctx.body = Cache 346 | return 347 | } else { 348 | ret = await nm.artist(ctx.params.id) 349 | cache.set('nm:artist:' + ctx.params.id, ret, 60 * 60 * 2) // Cache 2 Hour 350 | if (ret.code === 200) { 351 | cache.set('nm:album:' + ctx.params.id, ret, 60 * 60 * 2) // Cache 2 Hour 352 | } 353 | } 354 | ctx.body = ret || { 355 | code: 400, 356 | message: 'API 在请求时出现了问题,再试一下看看?', 357 | feedback: '访问 /status 获得详细信息', 358 | now: new Date().toString() 359 | } 360 | } 361 | 362 | // Album API 363 | controllers.album = async (ctx, next) => { 364 | let ret 365 | let Cache = await cache.get('nm:album:' + ctx.params.id, false) 366 | if (Cache) { 367 | ctx.set('Content-Type', 'application/json') 368 | ctx.body = Cache 369 | return 370 | } else { 371 | ret = await nm.album(ctx.params.id) 372 | if (ret.code === 200) { 373 | cache.set('nm:album:' + ctx.params.id, ret, 60 * 60 * 2) // Cache 2 Hour 374 | } 375 | } 376 | ctx.body = ret || { 377 | code: 400, 378 | message: 'API 在请求时出现了问题,再试一下看看?', 379 | feedback: '访问 /status 获得详细信息', 380 | now: new Date().toString() 381 | } 382 | } 383 | 384 | // Lyric API 385 | controllers.lyric = async (ctx, next) => { 386 | let ret 387 | let Cache = await cache.get('nm:lyric:' + ctx.params.id, false) 388 | if (Cache) { 389 | ctx.set('Content-Type', 'application/json') 390 | ctx.body = Cache 391 | return 392 | } else { 393 | ret = await nm.lyric(ctx.params.id) 394 | cache.set('nm:lyric:' + ctx.params.id, ret, 60 * 60 * 2) // Cache 2 Hour 395 | } 396 | ctx.body = ret || { 397 | code: 400, 398 | message: 'API 在请求时出现了问题,再试一下看看?', 399 | feedback: '访问 /status 获得详细信息', 400 | now: new Date().toString() 401 | } 402 | } 403 | 404 | // Music URL API 405 | controllers.url = async (ctx, next) => { 406 | const ret = await nm.url(ctx.params.id) 407 | ctx.body = ret || { 408 | code: 400, 409 | message: 'API 在请求时出现了问题,再试一下看看?', 410 | feedback: '访问 /status 获得详细信息', 411 | now: new Date().toString() 412 | } 413 | } 414 | 415 | // Song Detail API 416 | controllers.detail = async (ctx, next) => { 417 | let ret 418 | let Cache = await cache.get('nm:detail:' + ctx.params.id, false) 419 | if (Cache) { 420 | ctx.set('Content-Type', 'application/json') 421 | ctx.body = Cache 422 | return 423 | } else { 424 | ret = await nm.song(ctx.params.id) 425 | if (ret.songs.length > 0) { 426 | cache.set('nm:detail:' + ctx.params.id, ret, 60 * 60 * 2) 427 | } 428 | } 429 | ctx.body = ret || { 430 | code: 400, 431 | message: 'API 在请求时出现了问题,再试一下看看?', 432 | feedback: '访问 /status 获得详细信息', 433 | now: new Date().toString() 434 | } 435 | } 436 | 437 | const Response400 = (ctx, code = 0) => { 438 | switch (code) { 439 | case 1: 440 | ctx.status = 400 441 | ctx.body = { 442 | code: 400, 443 | message: 'id 数超出限制,最多为 20 个', 444 | feedback: '访问 /status 获得详细信息', 445 | ts: Date.now() 446 | } 447 | break 448 | 449 | default: 450 | ctx.status = 400 451 | ctx.body = { 452 | code: 400, 453 | message: '请求错误,乐曲不存在或者版权受限', 454 | feedback: '访问 /status 获得详细信息', 455 | ts: Date.now() 456 | } 457 | break 458 | } 459 | } 460 | 461 | const silentSummary = async (id, ctx) => { 462 | // First, Get Music URL 463 | const URLs = await nm.url(id) 464 | if (URLs && URLs.data && URLs.data.length > 0) { 465 | const ids = [] 466 | for (let _ of URLs.data) { 467 | if (_.code !== 404 && _.url) { 468 | ids.push(_.id) 469 | } 470 | } 471 | if (ids.length > 0) { 472 | const result = await pify(async).mapLimit(ids, 5, async id => { 473 | if (ctx.query && ctx.query.lyric) { 474 | const ret = await Promise.all([handleSummary(id, nconf.get('url'), true), getLyric(id, true)]) 475 | return ret 476 | } else { 477 | const ret = await Promise.all([handleSummary(id, nconf.get('url'), true)]) 478 | return ret 479 | } 480 | }) 481 | return result 482 | } else { 483 | return false 484 | } 485 | } else { 486 | return false 487 | } 488 | } 489 | 490 | const quickSummary = async (ID, ctx) => { 491 | const ids = ID.split(',') 492 | const result = await pify(async).mapLimit(ids, 5, async id => { 493 | if (ctx.query && ctx.query.lyric) { 494 | return Promise.all([handleSummary(id, nconf.get('url')), getLyric(id)]) 495 | } else { 496 | return Promise.all([handleSummary(id, nconf.get('url'))]) 497 | } 498 | }) 499 | return result 500 | } 501 | 502 | const handleSummary = async (id, url, check = false) => { 503 | let Cache 504 | Cache = await cache.get('nm:song:' + id) 505 | if (Cache) { 506 | return Cache 507 | } 508 | 509 | const cacheTime = check ? 60 * 60 * 24 * 7 : 60 * 60 * 2 510 | const data = {} 511 | data.id = id 512 | data.url = url + '/nm/redirect/music/' + id 513 | 514 | // Get Music Detail 515 | let detail 516 | Cache = await cache.get('nm:detail:' + id) 517 | if (Cache) { 518 | detail = Cache 519 | } else { 520 | detail = await nm.song(id.toString()) 521 | cache.set('nm:detail:' + id, detail, cacheTime) 522 | } 523 | data.name = detail.songs[0].name 524 | data.artists = [] 525 | for (let artist of detail.songs[0].ar) { 526 | data.artists.push(artist.name) 527 | } 528 | data.album = {} 529 | data.album.id = detail.songs[0].al.id 530 | data.album.name = detail.songs[0].al.name 531 | 532 | let album 533 | Cache = await cache.get('nm:album:' + detail.songs[0].al.id) 534 | if (Cache) { 535 | album = Cache 536 | } else { 537 | album = await nm.album(detail.songs[0].al.id.toString()) 538 | cache.set('nm:album:' + detail.songs[0].al.id, album, cacheTime) // Cache 2 Hour 539 | } 540 | data.album.picture = (await nm.picture(album.songs[0].al.pic_str)).url 541 | cache.set('nm:song:' + id, data, cacheTime) 542 | return data 543 | } 544 | 545 | const getLyric = async (id, check = false) => { 546 | let Cache 547 | Cache = await cache.get('nm:lyricSeries:' + id) 548 | if (Cache) { 549 | return Cache 550 | } 551 | const cacheTime = check ? 60 * 60 * 24 * 7 : 60 * 60 * 2 552 | // Get Lyric 553 | let lyric 554 | Cache = await cache.get('nm:lyric:' + id) 555 | if (Cache) { 556 | lyric = Cache 557 | } else { 558 | lyric = await nm.lyric(id.toString()) 559 | cache.set('nm:lyric:' + id, lyric, cacheTime) 560 | } 561 | const data = {} 562 | data.id = id 563 | data.lyric = {} 564 | data.lyric.base = (lyric.lrc && lyric.lrc.lyric) ? lyric.lrc.lyric : '[00:00.00] 纯音乐,敬请聆听。\n' 565 | data.lyric.translate = (lyric.tlyric && lyric.tlyric.lyric) ? lyric.tlyric.lyric : null 566 | cache.set('nm:lyricSeries:' + id, data, cacheTime) 567 | return data 568 | } 569 | 570 | const handleResult = (result, common = false) => { 571 | const data = {} 572 | const ids = [] 573 | if (common) { 574 | data.songs = [] 575 | } 576 | for (let _ of result) { 577 | const id = _[0].id 578 | ids.push(id) 579 | if (common) { 580 | const song = {} 581 | for (let $ of _) { 582 | Object.assign(song, $) 583 | } 584 | data.songs.push(song) 585 | } else { 586 | data[id] = {} 587 | for (let $ of _) { 588 | Object.assign(data[id], $) 589 | } 590 | } 591 | } 592 | data.ids = ids 593 | return data 594 | } 595 | 596 | module.exports = controllers 597 | -------------------------------------------------------------------------------- /src/controllers/status.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Import Packages 3 | const os = require('os') 4 | const path = require('path') 5 | const nconf = require('nconf') 6 | const cache = require(path.join(__dirname, '../cache')) 7 | const _ = require('lodash') 8 | const winston = require('winston') 9 | 10 | async function getAllRequests () { 11 | const requests = await cache.get('requests') 12 | return requests 13 | } 14 | 15 | async function getAllPastMinute () { 16 | const ts = parseInt(Date.now().toString().slice(0, 10)) - 60 17 | const requests = await cache.get('requests:count:' + ts.toString()) 18 | return requests 19 | } 20 | 21 | async function getAllPastHour () { 22 | const ts = parseInt(Date.now().toString().slice(0, 10)) - 60 * 60 23 | const requests = await cache.get('requests:count:' + ts.toString()) 24 | return requests 25 | } 26 | 27 | async function getAllPastDay () { 28 | const ts = parseInt(Date.now().toString().slice(0, 10)) - 60 * 60 * 24 29 | const requests = await cache.get('requests:count:' + ts.toString()) 30 | return requests 31 | } 32 | 33 | async function getHosts () { 34 | const requests = await cache.get('requests:hosts') 35 | return requests 36 | } 37 | 38 | async function getHostsPastMinute () { 39 | const ts = parseInt(Date.now().toString().slice(0, 10)) - 60 40 | const requests = await cache.get('requests:hosts:count:' + ts.toString()) 41 | return requests 42 | } 43 | 44 | async function getHostsPastHour () { 45 | const ts = parseInt(Date.now().toString().slice(0, 10)) - 60 * 60 46 | const requests = await cache.get('requests:hosts:count:' + ts.toString()) 47 | return requests 48 | } 49 | 50 | async function getHostsPastDay () { 51 | const ts = parseInt(Date.now().toString().slice(0, 10)) - 60 * 60 * 24 52 | const requests = await cache.get('requests:hosts:count:' + ts.toString()) 53 | return requests 54 | } 55 | 56 | async function getAllDayMap (now) { 57 | const ts = parseInt(Date.now().toString().slice(0, 10)) 58 | const events = [] 59 | for (let index = 1; index < 26; index++) { 60 | events.push(cache.get('requests:count:' + (ts - index * 60 * 60).toString())) 61 | } 62 | const result = await Promise.all(events) 63 | const data = [] 64 | data.push(now - parseInt(result[0])) 65 | for (let index = 0; index < (result.length - 2); index++) { 66 | data.push(parseInt(result[index]) - parseInt(result[index + 1])) 67 | } 68 | return data 69 | } 70 | 71 | async function getHostsDayMap (limitHosts, now) { 72 | const ts = parseInt(Date.now().toString().slice(0, 10)) 73 | const events = [] 74 | for (let index = 1; index < 26; index++) { 75 | events.push(cache.get('requests:hosts:count:' + (ts - index * 60 * 60).toString())) 76 | } 77 | const result = await Promise.all(events) 78 | const data = {} 79 | for (let host of limitHosts) { 80 | const _ = result[0] ? now[host] - parseInt(result[0][host]) : 0 81 | data[host] = {} 82 | data[host].dayMap = [] 83 | data[host].dayMap.push(_) 84 | } 85 | for (let index = 0; index < (result.length - 2); index++) { 86 | for (let host of limitHosts) { 87 | const _ = result[index] && result[index + 1] ? parseInt(result[index][host]) - parseInt(result[index + 1][host]) : null 88 | data[host].dayMap.push(_) 89 | } 90 | } 91 | return data 92 | } 93 | 94 | async function getPast5MinuteMap (now) { 95 | const ts = parseInt(Date.now().toString().slice(0, 10)) 96 | const events = [] 97 | for (let index = 1; index < 7; index++) { 98 | events.push(cache.get('requests:count:' + (ts - index * 60).toString())) 99 | } 100 | const result = await Promise.all(events) 101 | const data = [] 102 | data.push(now - parseInt(result[0])) 103 | for (let index = 0; index < (result.length - 2); index++) { 104 | data.push(parseInt(result[index]) - parseInt(result[index + 1])) 105 | } 106 | return data 107 | } 108 | module.exports = async (ctx, next) => { 109 | const pkg = require(path.join('../../', 'package')) 110 | const fetchData = await Promise.all([ 111 | // fetch All Requests 112 | getAllRequests(), 113 | getAllPastMinute(), 114 | getAllPastHour(), 115 | getAllPastDay(), 116 | // fetch hosts 117 | getHosts(), 118 | getHostsPastMinute(), 119 | getHostsPastHour(), 120 | getHostsPastDay() 121 | ]) 122 | const all = {} 123 | all.now = fetchData[0] 124 | all.pastMinute = fetchData[1] 125 | all.pastHour = fetchData[2] 126 | all.pastDay = fetchData[3] 127 | 128 | let hosts = {} 129 | // Generate totals 130 | let limitHost = [ 131 | // please add your own host 132 | 'example.com' 133 | ] 134 | const HostToDelete = [] 135 | for (let i of limitHost) { 136 | if (!fetchData[4][i]) { 137 | // if not exist 138 | winston.verbose(`host be removed: ${i}`) 139 | HostToDelete.push(i) 140 | } else { 141 | hosts[i] = {} 142 | hosts[i].total = fetchData[4][i] 143 | hosts[i].pastMinute = fetchData[5] ? parseInt(fetchData[4][i]) - parseInt(fetchData[5][i]) : null 144 | hosts[i].pastHour = fetchData[6] ? parseInt(fetchData[4][i]) - parseInt(fetchData[6][i]) : null 145 | hosts[i].pastDay = fetchData[7] ? parseInt(fetchData[4][i]) - parseInt(fetchData[7][i]) : null 146 | } 147 | } 148 | _.pullAll(limitHost, HostToDelete) 149 | // fetch DayMap 150 | const fetchDayMap = await Promise.all([ 151 | getAllDayMap(all.now), 152 | getHostsDayMap(limitHost, fetchData[4]), 153 | getPast5MinuteMap(all.now) 154 | ]) 155 | all.dayMap = fetchDayMap[0] 156 | all.FiveMinuteMap = fetchDayMap[2] 157 | // console.log(limitHost) 158 | for (let host of limitHost) { 159 | Object.assign(hosts[host], fetchDayMap[1][host]) 160 | } 161 | // hosts = Object.assign({}, hosts, fetchDayMap[1]) 162 | 163 | // get memory usage 164 | let memoryUsage = 0 165 | for (let v of Object.values(process.memoryUsage())) { 166 | memoryUsage += parseInt(v) 167 | } 168 | ctx.body = { 169 | name: pkg.name, 170 | version: pkg.version, 171 | message: 'Love us? donate at https://hitokoto.cn/donate', 172 | website: 'https://hitokoto.cn', 173 | server_id: nconf.get('api_name') ? nconf.get('api_name') : 'unallocated', 174 | server_status: { 175 | memory: { 176 | totol: os.totalmem() / (1024 * 1024), 177 | free: os.freemem() / (1024 * 1024), 178 | usage: memoryUsage / (1024 * 1024) 179 | }, 180 | // cpu: os.cpus(), 181 | load: os.loadavg() 182 | /* hitokto: { 183 | total: global.hitokoto.total, 184 | categroy: global.hitokoto.categroy, 185 | lastUpdate: global.hitokoto.lastUpdated 186 | } */ 187 | }, 188 | requests: { 189 | all: { 190 | total: parseInt(all.now), 191 | pastMinute: parseInt(all.now) - parseInt(all.pastMinute), 192 | pastHour: parseInt(all.now) - parseInt(all.pastHour), 193 | pastDay: parseInt(all.now) - parseInt(all.pastDay), 194 | dayMap: all.dayMap, 195 | FiveMinuteMap: all.FiveMinuteMap 196 | }, 197 | hosts 198 | }, 199 | feedback: { 200 | Kuertianshi: 'i@loli.online', 201 | freejishu: 'i@freejishu.com', 202 | a632079: 'a632079@qq.com' 203 | }, 204 | copyright: 'MoeCraft © ' + new Date().getFullYear() + ' All Rights Reserved. Powered by Teng-koa ( https://github.com/a632079/teng-koa ).', 205 | now: new Date(Date.now()).toString(), 206 | ts: Date.now() 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/cron.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Import Packages 3 | const winston = require('winston') 4 | const path = require('path') 5 | const fs = require('fs') 6 | const colors = require('colors/safe') 7 | const CronJob = require('cron').CronJob 8 | 9 | // Load Cron 10 | class cron { 11 | static async load () { 12 | try { 13 | // load crons 14 | let crons = await this.autoLoad() 15 | const cronMap = require(path.join(__dirname, '../crons.js'))(crons) 16 | if (cronMap === true) { 17 | // AutoLoad All Crons 18 | crons = await this.autoLoad(true) 19 | await crons.map((item, index, input) => { 20 | // Register CronJob 21 | const job = new CronJob(item[0], item[1], item[2], item[3], item[4]) 22 | job.start() 23 | }) 24 | winston.verbose('All Cron Jobs Load done.') 25 | } else { 26 | await cronMap.map((item, index, input) => { 27 | // Register CronJob 28 | const job = new CronJob(item[0], item[1], item[2], item[3], item[4]) 29 | job.start() 30 | }) 31 | winston.verbose('All Cron Jobs Load done.') 32 | } 33 | } catch (e) { 34 | winston.error(colors.red(e)) 35 | process.exit(1) 36 | } 37 | } 38 | 39 | static async autoLoad (isArray) { 40 | try { 41 | // Load Crons 42 | let crons = {} 43 | const dir = fs.readdirSync(path.join(__dirname, '../', './src/crons')) 44 | if (isArray) { 45 | await dir.map((item, index, input) => { 46 | crons[index] = module.parent.require(path.join(__dirname, '../', './src/crons/' + item)) 47 | }) 48 | } else { 49 | await dir.map((item, index, input) => { 50 | crons[item.substring(0, item.length - 3)] = module.parent.require(path.join(__dirname, '../', './src/crons/' + item)) 51 | }) 52 | } 53 | return crons 54 | } catch (e) { 55 | winston.error(colors.red(e)) 56 | process.exit(1) 57 | } 58 | } 59 | } 60 | module.exports = cron 61 | -------------------------------------------------------------------------------- /src/crons/countRequests.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Import Packages 3 | const winston = require('winston') 4 | const path = require('path') 5 | const cache = require(path.join(__dirname, '../cache')) 6 | 7 | function saveCount (ts, count) { 8 | cache.set('requests:count:' + ts, count, 60 * 60 * 25) 9 | .catch(err => { 10 | winston.error(err) 11 | saveCount(ts, count) 12 | }) 13 | } 14 | 15 | function saveHostsCount (ts, count) { 16 | cache.set('requests:hosts:count:' + ts, count, 60 * 60 * 25) 17 | .catch(err => { 18 | winston.error(err) 19 | saveCount(ts, count) 20 | }) 21 | } 22 | function autoSave (ts, requests) { 23 | saveCount(ts, requests.all) 24 | winston.debug('Save All requests total to Cache. Requests: ' + requests.all) 25 | saveHostsCount(ts, requests.hosts) 26 | winston.debug('Save Host requests total to Cache. Requests: ' + requests.hosts) 27 | } 28 | module.exports = [ 29 | '* * * * * *', // Cron Config 30 | () => { 31 | // Do something 32 | const ts = Date.now().toString().slice(0, 10) 33 | if (!global.requests) { 34 | Promise.all([cache.get('requests'), cache.get('requests:hosts')]) 35 | .then(data => { 36 | // Init 37 | global.requests = {} 38 | global.requests.all = data[0] || 0 39 | global.requests.hosts = data[1] || {} 40 | return global.requests 41 | }) 42 | .then(requests => { 43 | autoSave(ts, requests) 44 | }) 45 | } else { 46 | autoSave(ts, global.requests) 47 | } 48 | }, 49 | () => { 50 | // This function is executed when the job stops 51 | winston.error('Count Requests job is stopped. Kill process.') 52 | process.exit(1) 53 | }, 54 | true, // Start the job right now, 55 | 'Asia/Shanghai' // Timezone 56 | ] 57 | -------------------------------------------------------------------------------- /src/crons/dumpOriginStatistics.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Import Packages 3 | const winston = require('winston') 4 | const path = require('path') 5 | const cache = require(path.join(__dirname, '../cache')) 6 | 7 | module.exports = [ 8 | '* * * * * *', // Cron Config 9 | () => { 10 | // Do something 11 | if (global.originStatistics) { 12 | cache.set(`statistics:orgin:${new Date(new Date().toDateString()).getTime()}`, global.originStatistics, 60 * 60 * 25) 13 | } 14 | }, 15 | () => { 16 | // This function is executed when the job stops 17 | winston.error('Count Requests job is stopped. Kill process.') 18 | process.exit(1) 19 | }, 20 | true, // Start the job right now, 21 | 'Asia/Shanghai' // Timezone 22 | ] 23 | -------------------------------------------------------------------------------- /src/crons/resetOriginStatistics.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Import Packages 3 | const winston = require('winston') 4 | // const path = require('path') 5 | // const cache = require(path.join(__dirname, '../cache')) 6 | 7 | module.exports = [ 8 | '* * * * * *', // Cron Config 9 | () => { 10 | // Do something 11 | global.originStatistics = {} 12 | }, 13 | (job) => { 14 | // This function is executed when the job stops 15 | winston.error('Count Requests job is stopped. Kill process.') 16 | process.exit(1) 17 | }, 18 | true, // Start the job right now, 19 | 'Asia/Shanghai' // Timezone 20 | ] 21 | -------------------------------------------------------------------------------- /src/db.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize') 2 | const nconf = require('nconf') 3 | const path = require('path') 4 | const winston = require('winston') 5 | 6 | class db { 7 | constructor (model) { 8 | this.connect() 9 | return this.registerModel(model) 10 | } 11 | static async connect () { 12 | if (this.sequelize) { 13 | return this.sequelize 14 | } else { 15 | const type = nconf.get('database') 16 | const config = { 17 | password: nconf.get(type + ':password'), 18 | username: nconf.get(type + ':username'), 19 | database: nconf.get(type + ':database'), 20 | host: nconf.get(type + ':host'), 21 | port: nconf.get(type + ':port') 22 | } 23 | const sequelize = new Sequelize(config.database, config.username, config.password, { 24 | host: config.host, 25 | port: config.port, 26 | dialect: type, 27 | pool: { 28 | max: 5, 29 | min: 0, 30 | acquire: 30000, 31 | idle: 10000 32 | }, 33 | logging: (log) => { 34 | winston.debug(log) 35 | }, 36 | operatorsAliases: false 37 | }) 38 | // Test Connection 39 | await sequelize.authenticate() 40 | .then(() => { 41 | winston.verbose('Database Connection has been established successfully.') 42 | }) 43 | .catch(err => { 44 | winston.error(err.message) 45 | }) 46 | 47 | this.sequelize = sequelize 48 | return sequelize 49 | } 50 | } 51 | static async registerModel (model) { 52 | await this.connect() 53 | if (this[model]) { 54 | return this[model] 55 | } else { 56 | // Register Model 57 | const modelArray = require(path.join(__dirname, '../', './src/models/databases', model)) 58 | this[model] = this.sequelize.define(model, modelArray[0], modelArray[1]) 59 | return this[model] 60 | } 61 | } 62 | 63 | static get () { 64 | this.connect() 65 | return this 66 | } 67 | } 68 | 69 | module.exports = db 70 | -------------------------------------------------------------------------------- /src/logger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | 'use strict' 5 | 6 | const Counter = require('passthrough-counter') 7 | const humanize = require('humanize-number') 8 | const bytes = require('bytes') 9 | const chalk = require('chalk') 10 | const winston = require('winston') 11 | 12 | /** 13 | * Expose logger. 14 | */ 15 | 16 | module.exports = dev 17 | 18 | /** 19 | * Color map. 20 | */ 21 | 22 | const colorCodes = { 23 | 7: 'magenta', 24 | 5: 'red', 25 | 4: 'yellow', 26 | 3: 'cyan', 27 | 2: 'green', 28 | 1: 'green', 29 | 0: 'yellow' 30 | } 31 | 32 | /** 33 | * Development logger. 34 | */ 35 | 36 | function dev (opts) { 37 | return async function logger (ctx, next) { 38 | // request 39 | const start = Date.now() 40 | winston.verbose(' ' + chalk.gray('<--') + 41 | ' ' + chalk.bold('%s') + 42 | ' ' + chalk.gray('%s') + 43 | ' ' + chalk.gray('%s'), 44 | ctx.method, 45 | ctx.originalUrl, ctx.reqId) 46 | 47 | try { 48 | await next() 49 | } catch (err) { 50 | // log uncaught downstream errors 51 | log(ctx, start, null, err) 52 | throw err 53 | } 54 | 55 | // calculate the length of a streaming response 56 | // by intercepting the stream with a counter. 57 | // only necessary if a content-length header is currently not set. 58 | const length = ctx.response.length 59 | const body = ctx.body 60 | let counter 61 | if (length == null && body && body.readable) { 62 | ctx.body = body 63 | .pipe(counter = Counter()) 64 | .on('error', ctx.onerror) 65 | } 66 | 67 | // log when the response is finished or closed, 68 | // whichever happens first. 69 | const res = ctx.res 70 | 71 | const onfinish = done.bind(null, 'finish') 72 | const onclose = done.bind(null, 'close') 73 | 74 | res.once('finish', onfinish) 75 | res.once('close', onclose) 76 | 77 | function done (event) { 78 | res.removeListener('finish', onfinish) 79 | res.removeListener('close', onclose) 80 | log(ctx, start, counter ? counter.length : length, null, event) 81 | } 82 | } 83 | } 84 | 85 | /** 86 | * Log helper. 87 | */ 88 | 89 | function log (ctx, start, len, err, event) { 90 | // get the status code of the response 91 | const status = err 92 | ? (err.isBoom ? err.output.statusCode : err.status || 500) 93 | : (ctx.status || 404) 94 | 95 | // set the color of the status code; 96 | const s = status / 100 | 0 97 | const color = colorCodes.hasOwnProperty(s) ? colorCodes[s] : 0 98 | 99 | // get the human readable response length 100 | let length 101 | if (~[204, 205, 304].indexOf(status)) { 102 | length = '' 103 | } else if (len == null) { 104 | length = '-' 105 | } else { 106 | length = bytes(len).toLowerCase() 107 | } 108 | 109 | const upstream = err ? chalk.red('xxx') 110 | : event === 'close' ? chalk.yellow('-x-') 111 | : chalk.gray('-->') 112 | 113 | winston.verbose(' ' + upstream + 114 | ' ' + chalk.bold('%s') + 115 | ' ' + chalk.gray('%s') + 116 | ' ' + chalk[color]('%s') + 117 | ' ' + chalk.gray('%s') + 118 | ' ' + chalk.gray('%s'), 119 | ctx.method, 120 | ctx.originalUrl, 121 | status, 122 | time(start), 123 | length) 124 | } 125 | 126 | /** 127 | * Show the response time in a human readable format. 128 | * In milliseconds if less than 10 seconds, 129 | * in seconds otherwise. 130 | */ 131 | 132 | function time (start) { 133 | const delta = Date.now() - start 134 | return humanize(delta < 10000 135 | ? delta + 'ms' 136 | : Math.round(delta / 1000) + 's') 137 | } 138 | -------------------------------------------------------------------------------- /src/mail.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const nconf = require('nconf') 3 | const winston = require('winston') 4 | const bluebird = require('bluebird') 5 | const nodemailer = require('nodemailer') 6 | const pkg = require('../package') 7 | const htmlEscape = require('./utils').htmlEscape 8 | class mail { 9 | static async connect () { 10 | if (this.smtp) { 11 | return this.smtp 12 | } else { 13 | const config = { 14 | pool: true, // use pool 15 | host: nconf.get('mail:host'), 16 | port: nconf.get('mail:port') || 465, 17 | secure: nconf.get('mail:ssl'), // use TLS 18 | auth: { 19 | user: nconf.get('mail:username'), 20 | pass: nconf.get('mail:password') 21 | }, 22 | tls: { rejectUnauthorized: false } 23 | } 24 | const transporter = nodemailer.createTransport(config) 25 | await transporter.verify((err, success) => { 26 | if (err) { 27 | winston.error(err) 28 | process.exit(1) 29 | } else { 30 | winston.verbose('SMTP Connection Pool is ready.') 31 | } 32 | }) 33 | this.smtp = bluebird.promisifyAll(transporter) 34 | return this.smtp 35 | } 36 | } 37 | /* 38 | * msg - Object 39 | * -- to To Mail (String) 40 | * -- title Mail Title (String) 41 | * -- body Mail Body (String) 42 | * -- html is HTML Text (Bool) 43 | */ 44 | static async send (params) { 45 | await this.connect() 46 | const msg = params 47 | msg.subject = msg.title 48 | msg.from = pkg.name + ' <' + nconf.get('mail:username') + '>' 49 | msg.html ? msg.html = msg.body : msg.text = msg.body 50 | delete msg.body 51 | delete msg.title 52 | return this.smtp.sendMailAsync(msg) 53 | } 54 | static error (err) { 55 | // Send Error to Admins 56 | const admin = nconf.get('admin') 57 | const to = Array.isArray(admin) ? admin.join(',') : admin 58 | let html = htmlEscape(err.stack) 59 | html = '

错误报告!

触发时间: ' + new Date().toISOString() + '

错误细节:

' + html + '
' 60 | return this.send({ 61 | title: 'Crash! Error Report!', 62 | to: to, 63 | body: html, 64 | html: true 65 | }) 66 | } 67 | } 68 | module.exports = mail 69 | -------------------------------------------------------------------------------- /src/middlewares/MailError.js: -------------------------------------------------------------------------------- 1 | const mail = require('../mail') 2 | const formatError = require('../utils').formatError 3 | const nconf = require('nconf') 4 | const winston = require('winston') 5 | 6 | const shouldThrow404 = (status, body) => { 7 | return !status || (status === 404 && body == null) 8 | } 9 | 10 | const shouldEmitError = (err, status) => { 11 | return !err.expose && status >= 500 12 | } 13 | 14 | async function sendMail (ctx, next) { 15 | try { 16 | await next() 17 | // future proof status 18 | shouldThrow404(ctx.status, ctx.body) && ctx.throw(404) 19 | } catch (e) { 20 | // Format and set body 21 | ctx.body = formatError(e) || {} 22 | // Set status 23 | ctx.status = e.status || e.statusCode || 500 24 | // Mail Error 25 | if (nconf.get('admin') && ctx.status !== 404) { 26 | mail.error(e) 27 | } 28 | // Emit the error if we really care 29 | shouldEmitError(e, ctx.status) && winston.error(e) 30 | } 31 | } 32 | function init () { 33 | return sendMail 34 | } 35 | module.exports = init 36 | -------------------------------------------------------------------------------- /src/middlewares/countRequest.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | function countRequest () { 3 | const path = require('path') 4 | const cache = require(path.join(__dirname, '../', 'cache')) 5 | const winston = require('winston') 6 | return async (ctx, next) => { 7 | await next() 8 | // Wait Request Done 9 | if (!global.requests) { 10 | const fetchRequests = await Promise.all([cache.get('requests'), cache.get('requests:hosts')]) 11 | global.requests = {} 12 | global.requests.all = parseInt(fetchRequests[0]) || 0 13 | global.requests.hosts = fetchRequests[1] || {} 14 | } 15 | const host = String(ctx.request.host) 16 | global.requests.all++ 17 | global.requests.hosts[host] = typeof global.requests.hosts[host] === 'undefined' || global.requests.hosts[host] === null ? 0 : parseInt(global.requests.hosts[host]) + 1 18 | 19 | Promise.all([ 20 | cache.set('requests', global.requests.all), 21 | cache.set('requests:hosts', global.requests.hosts) 22 | ]) 23 | .catch(err => { 24 | winston.error(err) 25 | }) 26 | } 27 | } 28 | 29 | module.exports = countRequest 30 | -------------------------------------------------------------------------------- /src/middlewares/requestId.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const uuidV4 = require('uuid/v4') 4 | /** 5 | * Return middleware that gets an unique request id from a header or 6 | * generates a new id. 7 | * 8 | * @param {Object} [options={}] - Optional configuration. 9 | * @param {string} [options.header=X-Request-Id] - Request and response header name. 10 | * @param {string} [options.propertyName=reqId] - Context property name. 11 | * @param {function} [options.generator] - Id generator function. 12 | * @return {function} Koa middleware. 13 | */ 14 | 15 | function requestId (options = {}) { 16 | const { 17 | header = 'X-Request-Id', 18 | propertyName = 'reqId', 19 | generator = uuidV4 20 | } = options 21 | 22 | return (ctx, next) => { 23 | const reqId = ctx.request.get(header) || generator() 24 | ctx[propertyName] = reqId 25 | ctx.set(header, reqId) 26 | return next() 27 | } 28 | } 29 | 30 | module.exports = requestId 31 | -------------------------------------------------------------------------------- /src/middlewares/responseHandler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * HTTP Status codes 5 | */ 6 | const statusCodes = { 7 | CONTINUE: 100, 8 | OK: 200, 9 | CREATED: 201, 10 | ACCEPTED: 202, 11 | NO_CONTENT: 204, 12 | BAD_REQUEST: 400, 13 | UNAUTHORIZED: 401, 14 | FORBIDDEN: 403, 15 | NOT_FOUND: 404, 16 | REQUEST_TIMEOUT: 408, 17 | UNPROCESSABLE_ENTITY: 422, 18 | INTERNAL_SERVER_ERROR: 500, 19 | NOT_IMPLEMENTED: 501, 20 | BAD_GATEWAY: 502, 21 | SERVICE_UNAVAILABLE: 503, 22 | GATEWAY_TIME_OUT: 504 23 | } 24 | 25 | function responseHandler () { 26 | return async (ctx, next) => { 27 | ctx.res.statusCodes = statusCodes 28 | ctx.statusCodes = ctx.res.statusCodes 29 | 30 | ctx.res.success = (data = null, message = null) => { 31 | ctx.status = ctx.status < 400 ? ctx.status : statusCodes.OK 32 | ctx.body = { status: 'success', data, message } 33 | } 34 | 35 | ctx.res.fail = (code = null, message = null, data = null) => { 36 | ctx.status = ctx.status >= 400 && ctx.status < 500 37 | ? ctx.status 38 | : statusCodes.BAD_REQUEST 39 | ctx.body = { status: 'fail', code, data, message } 40 | } 41 | 42 | ctx.res.error = (code = null, message = null, data = null) => { 43 | ctx.status = ctx.status < 500 44 | ? statusCodes.INTERNAL_SERVER_ERROR 45 | : ctx.status 46 | ctx.body = { status: 'error', code, data, message } 47 | } 48 | 49 | ctx.res.ok = (data, message) => { 50 | ctx.status = statusCodes.OK 51 | ctx.res.success(data, message) 52 | } 53 | 54 | ctx.res.created = (data, message) => { 55 | ctx.status = statusCodes.CREATED 56 | ctx.res.success(data, message) 57 | } 58 | 59 | ctx.res.accepted = (data, message) => { 60 | ctx.status = statusCodes.ACCEPTED 61 | ctx.res.success(data, message) 62 | } 63 | 64 | ctx.res.noContent = (data, message) => { 65 | ctx.status = statusCodes.NO_CONTENT 66 | ctx.res.success(data, message) 67 | } 68 | 69 | ctx.res.badRequest = (code, message, data) => { 70 | ctx.status = statusCodes.BAD_REQUEST 71 | ctx.res.fail(code, message, data) 72 | } 73 | 74 | ctx.res.forbidden = (code, message, data) => { 75 | ctx.status = statusCodes.FORBIDDEN 76 | ctx.res.fail(code, message, data) 77 | } 78 | 79 | ctx.res.notFound = (code, message, data) => { 80 | ctx.status = statusCodes.NOT_FOUND 81 | ctx.res.fail(code, message, data) 82 | } 83 | 84 | ctx.res.unprocessableEntity = (code, message, data) => { 85 | ctx.status = statusCodes.UNPROCESSABLE_ENTITY 86 | ctx.res.fail(code, message, data) 87 | } 88 | 89 | ctx.res.internalServerError = (code, message, data) => { 90 | ctx.status = statusCodes.INTERNAL_SERVER_ERROR 91 | ctx.res.error(code, message, data) 92 | } 93 | 94 | ctx.res.notImplemented = (code, message, data) => { 95 | ctx.status = statusCodes.NOT_IMPLEMENTED 96 | ctx.res.error(code, message, data) 97 | } 98 | await next() 99 | } 100 | } 101 | 102 | module.exports = responseHandler 103 | -------------------------------------------------------------------------------- /src/models/behaviors/test.js: -------------------------------------------------------------------------------- 1 | // Database Behavior model 2 | -------------------------------------------------------------------------------- /src/models/databases/model.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = require('sequelize') 3 | -------------------------------------------------------------------------------- /src/models/databases/test.js: -------------------------------------------------------------------------------- 1 | // Load Base Model 2 | const model = require('./model') 3 | 4 | // Define Test Model 5 | module.exports = { 6 | id: { 7 | type: model.INTEGER, 8 | primaryKey: true, 9 | autoIncrement: true 10 | }, 11 | data: model.STRING 12 | } 13 | -------------------------------------------------------------------------------- /src/prestart.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const winston = require('winston') 3 | const config = require('../config') 4 | const nconf = require('nconf') 5 | const pkg = require('../package.json') 6 | const path = require('path') 7 | const fs = require('fs') 8 | const dirname = path.join(__dirname, '../') 9 | 10 | function setupWinston () { 11 | const logFile = config.log_path || path.join(__dirname, '../', './logs/', pkg.name + '.log') 12 | fs.existsSync(logFile) || fs.writeFileSync(logFile, '') 13 | winston.remove(winston.transports.Console) 14 | winston.add(winston.transports.File, { 15 | filename: logFile, 16 | level: config.log_level || (global.env === 'production' ? 'info' : 'verbose'), 17 | handleExceptions: true, 18 | maxsize: 5242880, 19 | maxFiles: 10 20 | }) 21 | winston.add(winston.transports.Console, { 22 | colorize: nconf.get('log-colorize') !== 'false', 23 | timestamp: function () { 24 | var date = new Date() 25 | return config.json_logging ? date.toJSON() 26 | : date.toISOString() + ' [' + global.process.pid + ']' 27 | }, 28 | level: config.log_level || (global.env === 'production' ? 'info' : 'verbose'), 29 | json: !!config.json_logging, 30 | stringify: !!config.json_logging 31 | }) 32 | } 33 | 34 | function loadConfig (configFile) { 35 | winston.verbose('* using configuration stored in: %s', configFile) 36 | 37 | nconf.file({ 38 | file: configFile 39 | }) 40 | 41 | nconf.defaults({ 42 | base_dir: dirname, 43 | version: pkg.version 44 | }) 45 | 46 | if (!nconf.get('isCluster')) { 47 | nconf.set('isPrimary', 'true') 48 | nconf.set('isCluster', 'false') 49 | } 50 | } 51 | 52 | function printCopyright () { 53 | const colors = require('colors/safe') 54 | const date = new Date() 55 | console.log(colors.bgBlue(colors.black(' ' + pkg.name + ' v' + pkg.version + ' © ' + date.getFullYear() + ' All Rights Reserved. ')) + ' ' + colors.bgRed(colors.black(' Powered by teng-koa '))) 56 | console.log('') 57 | console.log(colors.bgCyan(colors.black(' 我们一路奋战,不是为了改变世界,而是为了不让世界改变我们。 '))) 58 | } 59 | 60 | module.exports = { 61 | load: (config_file) => { 62 | if (!config_file) { 63 | config_file = path.join(__dirname, '../', 'config.json') 64 | } 65 | setupWinston() 66 | loadConfig(config_file) 67 | printCopyright() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/route.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const winston = require('winston') 4 | 5 | class route { 6 | constructor () { 7 | const Controller = require(path.join(__dirname, '../', './src/controller')) 8 | this.controller = new Controller() 9 | return this.routes() 10 | } 11 | 12 | async routes () { 13 | let routes 14 | await this.controller 15 | .then((controller) => { 16 | // Load RouteMap 17 | const Router = require('koa-router') 18 | const RouteMap = require(path.join(__dirname, '../', './routes'))(new Router(), controller) 19 | routes = RouteMap 20 | }) 21 | .catch(err => { 22 | winston.error(err) 23 | process.exit(1) 24 | }) 25 | return routes 26 | } 27 | } 28 | module.exports = route 29 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | const compact = require('lodash.compact') 2 | const curry = require('lodash.curry') 3 | const utils = {} 4 | 5 | const DEFAULT_PROPERTIES = [ 6 | 'name', 7 | 'message', 8 | 'stack', 9 | 'type' 10 | ] 11 | 12 | const toErrorObject = curry((err, acum, propertyName) => { 13 | return err[propertyName] ? Object.assign({}, acum, { 14 | [propertyName]: err[propertyName] 15 | }) : acum 16 | }) 17 | 18 | const DEFAULT_FORMATTING_PIPELINE = Object.freeze({ 19 | // Set all enumerable properties of error onto the object 20 | preFormat: err => Object.assign({}, err), 21 | // Add default custom properties to final error object 22 | format: function (err, preFormattedError) { 23 | const formattedError = DEFAULT_PROPERTIES.reduce(toErrorObject(err), {}) 24 | return Object.assign({}, preFormattedError, formattedError, { 25 | status: err.status || err.statusCode || 500 26 | }) 27 | }, 28 | // Final transformation after `options.format` (defaults to no op) 29 | postFormat: null 30 | }) 31 | // Extend options with default values 32 | const formatters = Object.assign({}, DEFAULT_FORMATTING_PIPELINE) 33 | 34 | const formattingPipeline = compact([ 35 | formatters.preFormat, 36 | formatters.format, 37 | formatters.postFormat 38 | ]) 39 | 40 | const applyFormat = curry((err, acum, formatter) => formatter(err, acum)) 41 | 42 | /** 43 | * Apply all ordered formatting functions to original error. 44 | * @param {Error} err The thrown error. 45 | * @return {Object} The JSON serializable formatted object. 46 | */ 47 | utils.formatError = err => { 48 | return formattingPipeline.reduce(applyFormat(err), {}) 49 | } 50 | 51 | utils.htmlEscape = text => { 52 | return text.replace(/[<>"&]/g, function (match, pos, originalText) { 53 | switch (match) { 54 | case '<': return '<' 55 | case '>':return '>' 56 | case '&':return '&' 57 | case '"':return '"' 58 | } 59 | }) 60 | } 61 | module.exports = utils 62 | -------------------------------------------------------------------------------- /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard" 3 | } -------------------------------------------------------------------------------- /test/unit/mail.js: -------------------------------------------------------------------------------- 1 | const preStart = require('../prestart') 2 | preStart.load() 3 | const expect = require('chai').expect 4 | 5 | describe('Test Mail', () => { 6 | const mail = require('../../src/mail') 7 | it('should be connect successful', async () => { 8 | const result = !!(await mail.connect()) 9 | expect(result).to.be.true 10 | }) 11 | it('Should be send successful', async() => { 12 | mail.send({ 13 | to: 'a632079@gmail.com', 14 | title: 'Hello', 15 | body: 'Happy New Year!', 16 | }) 17 | .then(res => { 18 | console.log(res) 19 | expect(!!res).to.be.true 20 | }) 21 | .catch(err => { 22 | console.log(err) 23 | expect(false).to.be.true 24 | }) 25 | // 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /test/unit/test.js: -------------------------------------------------------------------------------- 1 | // Write Your Unit Test Code 2 | -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | print_status() { 3 | local outp=$(echo "$1" | sed -r 's/\\n/\\n## /mg') 4 | echo 5 | echo -e "## ${outp}" 6 | echo 7 | } 8 | bail() { 9 | echo 'Error executing command, exiting' 10 | exit 1 11 | } 12 | exec_cmd_nobail() { 13 | echo "+ $1" 14 | sh -c "$1" 15 | } 16 | 17 | exec_cmd() { 18 | exec_cmd_nobail "$1" || bail 19 | } 20 | 21 | print_status "Update Procedure" 22 | exec_cmd "git pull" 23 | 24 | print_status "Update Repos" 25 | exec_cmd "pnpm i -g pnpm" 26 | exec_cmd "pnpm i" 27 | 28 | print_status "Restart Service" 29 | exec_cmd "pm2 restart hitokoto" 30 | -------------------------------------------------------------------------------- /usage.md: -------------------------------------------------------------------------------- 1 | # 使用 2 | ## 结构树 3 | ``` 4 | Teng-koa 5 | │ config.json 6 | │ core.js 7 | │ crons.js 8 | │ package.json 9 | │ plugins.js 10 | │ routes.js 11 | │ yarn.lock 12 | │ 13 | ├─logs 14 | │ readme.md 15 | │ 16 | ├─public 17 | │ favicon.ico 18 | │ 19 | ├─src 20 | │ │ cache.js 21 | │ │ controller.js 22 | │ │ cron.js 23 | │ │ db.js 24 | │ │ logger.js 25 | │ │ mail.js 26 | │ │ prestart.js 27 | │ │ route.js 28 | │ │ utils.js 29 | │ │ 30 | │ ├─controllers 31 | │ │ hello.js 32 | │ │ netease.js 33 | │ │ status.js 34 | │ │ 35 | │ ├─crons 36 | │ │ countRequests.js 37 | │ │ 38 | │ ├─middlewares 39 | │ │ countRequests.js 40 | │ │ MailError.js 41 | │ │ requestId.js 42 | │ │ responseHandler.js 43 | │ │ 44 | │ └─models 45 | │ ├─behaviors 46 | │ │ test.js 47 | │ │ 48 | │ └─databases 49 | │ model.js 50 | │ test.js 51 | │ 52 | └─test 53 | │ .eslintrc.json 54 | │ 55 | └─unit 56 | mail.js 57 | test.js 58 | 59 | ``` 60 | ## 中间件 / 插件 61 | 可以通过 `./plugins.js` 快速注册 koa 中间件/ 插件 62 | 63 | ## 路由 64 | 路由表放置在 `./routes.js` 65 | 可以根据需要在里面添加路由 66 | 67 | ## 控制器 68 | 控制器请放置在 `./src/controllers/` 目录下 69 | 该目录下的控制器会在启动时被自动注册,可以在路由表中很方便得调用控制器 70 | 控制器的暴露必须是 函数 或者 对象 (对象中包含函数) 71 | 两个在路由中的简单用例: 72 | * `controller.hello.index` ( hello 控制器暴露对象) 73 | * `controller.status` (status 控制器暴露函数) 74 | 75 | ## 计划任务 76 | 计划任务是基于 `Cron` 模块实现。 77 | 将不同的模块放置在 `./src/crons` 下, 然后就可以直接在 `./crons.js` 中直接注册。 78 | 框架提供了 自动注册 和 按需注册的方式, 在 `./crons.js` 中: 79 | * 暴露 `true` - 自动注册 `./src/crons` 下的所有 计划任务 80 | * 暴露 数组 - 按需注册计划任务 81 | 82 | ### 计划任务模板 83 | ``` 84 | module.exports = [ 85 | '* * * * * *', // Cron Config 86 | () => { 87 | // Do something 88 | }, 89 | () => { 90 | // This function is executed when the job stops 91 | }, 92 | true, // Start the job right now, 93 | 'Asia/Shanghai' // Timezone 94 | ] 95 | ``` 96 | 97 | 计划任务模块应该暴露一个 数组: 98 | * 数组第一项是 `crontab` 时间设置 99 | * 数组第二项是 触发计划任务时执行的函数 100 | * 数组第三项是 计划任务被停止时进行的函数 101 | * 数组第四项是 是否立即执行计划任务 (实际上,不管你填写 false 还是 true,我们都会立即执行计划任务) 102 | * 数组第五项是 时区的设置 103 | 104 | ## 数据库 105 | 数据库默认使用的 mysql, 基于 `sequelize` 的 ORM 实现。 106 | 数据库模板存放在 `./src/models/databases` 下,使用时可以直接调用 `db.registerModel' 方法直接注册模板。 107 | 108 | 在使用中,我们只需要配置好信息,然后: 109 | ``` 110 | const db = require(SrcDir + 'db') 111 | const user = await db.registerModel('model') // Async Function 112 | // Or Common Promise 113 | db.registerModel('model') 114 | .then(model => { 115 | // do something 116 | }) 117 | .catch(err => { 118 | // Catch err 119 | }) 120 | ``` 121 | 122 | 对于一些 `sequelize` 实例化之后才能使用的 参数, 比如 : `sequelize.rand()`。 123 | 我们可以通过其子对象进行调用,比如: `model.sequelize.rand()` 124 | 125 | > `behaviors` 是 行为库,这里的库通常暴露的方法是可以直接操作的(可以直接达到目的,比如 `behavior.getName(ids)`)。由于时间不多,会在 v0.0.4 补充 例子。 126 | > `migrations` 是 迁移库 ,目前尚未提交。会在 v0.0.4 详细介绍 127 | 128 | 129 | ## 缓存 130 | 缓存系统是基于 `Redis` 的实现。 131 | 简单的用例: 132 | ``` 133 | const cache = require(SrcDir + 'cache') 134 | // Async Function 135 | const get = await cache.get(key) 136 | let set = await cache.set(key, value) // Forever key 137 | set = await cache.set(key, value, time) // Redis Time Level is EX 138 | 139 | // Common Promise 140 | cache.get(key) 141 | .then(value => { 142 | // do something 143 | }) 144 | .catch(err => { 145 | // catch error 146 | }) 147 | ``` 148 | 149 | 需要注意的是,`set` 方法也返回 promise 。 150 | 在通常的用例中,这有助于控制流程和捕获错误。 151 | 152 | ## 邮件 153 | 邮件是基于 `nodemailer` 的实现。 154 | 155 | 暴露了两个接口: 156 | * `mail.send(msg)` 157 | * `mail.error(e)` 158 | 159 | `msg` 的 参数: 160 | ``` 161 | { 162 | to: '', 163 | title: '', 164 | body: '', 165 | html: false 166 | } 167 | ``` 168 | 169 | 简单的用例: 170 | 171 | ``` 172 | const mail = require(SrcDir + 'mail') 173 | await mail.send({ 174 | to: 'i@a632079.me', 175 | title: 'Hello World', 176 | body: 'Test Mail', 177 | html: false 178 | }) 179 | ``` 180 | 181 | ## 日记 182 | 日记保存在 `./logs`。 183 | 默认的日记系统使用的是 `winston` 184 | 可以通过配置 `log_level` 来控制日记级别 185 | 186 | ## More 187 | 由于时间仓促,这次就先这些简单的内容吧。 下周,会将文档更新得更加全面。 188 | --------------------------------------------------------------------------------