├── common └── common.json ├── public └── stylesheets │ └── style.css ├── middleware └── times.js ├── package.json ├── pm2.json ├── .gitignore ├── README.md ├── bin └── www ├── routes ├── index.js ├── chapter.js ├── source.js ├── search.js ├── book.js ├── ranking.js └── article.js └── app.js /common/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "API": "http://api.zhuishushenqi.com", 3 | "PIC": "//statics.zhuishushenqi.com", 4 | "CHAPTER": "http://chapter2.zhuishushenqi.com" 5 | } -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /middleware/times.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment') 2 | /** 3 | * 设置禁止访问的时间段 4 | * @param start [Number] 5 | * @param end [Number] 6 | */ 7 | function times(start, end) { 8 | return function(req, res, next) { 9 | const hour = moment().hour() 10 | if (hour >= start || hour < end) { 11 | return res.send(JSON.stringify({ "flag": 0, "msg": `快去睡觉吧,${start}点至${end}点无法访问` })); 12 | } else { 13 | next() 14 | } 15 | } 16 | } 17 | 18 | module.exports = times -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "novel-api", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www", 7 | "doc": "apidoc -i routes/ -o public/", 8 | "deploy": "pm2 start pm2.json" 9 | }, 10 | "dependencies": { 11 | "cookie-parser": "~1.4.3", 12 | "debug": "~2.6.9", 13 | "express": "~4.16.0", 14 | "file-stream-rotator": "^0.2.1", 15 | "moment": "^2.24.0", 16 | "morgan": "~1.9.0", 17 | "request": "^2.86.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "novel-api", 3 | "script": "./bin/www", 4 | "cwd": "./", 5 | "watch": [ 6 | "bin", 7 | "routers" 8 | ], 9 | "ignore_watch": [ 10 | "node_modules", 11 | "logs", 12 | "public", 13 | "log" 14 | ], 15 | "watch_options": { 16 | "followSymlinks": false 17 | }, 18 | "max_memory_restart": "1G", 19 | "error_file": "./logs/novel-apierr.log", 20 | "out_file": "./logs/novel-api-out.log", 21 | "env": { 22 | "DEBUG": "novel-api", 23 | "PORT": "8080" 24 | } 25 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # public 64 | public/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # novel-api 2 | 一个小说接口API服务 3 | 4 | 1.0版本: 5 | 6 | 技术栈使用express-generator、express、request、morgan、file-stream-rotator。接口用追书神器API。 7 | 线上访问地址https://api.langpz.com/ 8 | 9 | 目前接口设计有首页,小说详情页,搜索,小说文章列表页,排行API。 10 | ``` 11 | git clone https://github.com/lanpangzhi/novel-api.git 12 | cd novel-api 13 | npm install 14 | // Linux MacOS 15 | DEBUG=novel-api:* & npm start 16 | // windows 17 | set DEBUG=novel-api:* & npm start 18 | ``` 19 | 20 | 生成api文档 21 | ``` 22 | npm run doc 23 | 需要自己配置nginx解析静态资源 24 | 生成的文档在public目录下 25 | ``` 26 | 27 | - [使用Express开发小说API接口服务1.0(一)](http://blog.langpz.com/%E4%BD%BF%E7%94%A8Express%E5%BC%80%E5%8F%91%E5%B0%8F%E8%AF%B4API%E6%8E%A5%E5%8F%A3%E6%9C%8D%E5%8A%A1.html) 28 | - [使用Express开发小说API接口服务1.0(二)](http://blog.langpz.com/%E4%BD%BF%E7%94%A8Express%E5%BC%80%E5%8F%91%E5%B0%8F%E8%AF%B4API%E6%8E%A5%E5%8F%A3%E6%9C%8D%E5%8A%A1-%E4%BA%8C.html) 29 | - [使用Express开发小说API接口服务1.0(三)](http://blog.langpz.com/%E4%BD%BF%E7%94%A8Express%E5%BC%80%E5%8F%91%E5%B0%8F%E8%AF%B4API%E6%8E%A5%E5%8F%A3%E6%9C%8D%E5%8A%A11-0%EF%BC%88%E4%B8%89%EF%BC%89.html) 30 | - [使用apidoc文档神器,快速生成api文档](http://blog.langpz.com/%E4%BD%BF%E7%94%A8apidoc%E6%96%87%E6%A1%A3%E7%A5%9E%E5%99%A8%EF%BC%8C%E5%BF%AB%E9%80%9F%E7%94%9F%E6%88%90api%E6%96%87%E6%A1%A3.html) 31 | - [部署小说api服务到腾讯云](http://blog.langpz.com/%E9%83%A8%E7%BD%B2%E5%B0%8F%E8%AF%B4api%E6%9C%8D%E5%8A%A1%E5%88%B0%E8%85%BE%E8%AE%AF%E4%BA%91.html) 32 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('novel-api:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | let express = require('express'); 2 | let request = require('request'); 3 | let common = require('../common/common.json'); // 引用公共文件 4 | let router = express.Router(); 5 | 6 | /** 7 | * @api {get} /index 请求首页数据 8 | * @apiVersion 1.0.0 9 | * @apiName 获取首页数据 10 | * @apiGroup index 11 | * 12 | * 13 | * @apiSuccess {Number} flag 是否获取到数据 1成功 0失败 14 | * @apiSuccess {Array} books 返回书籍内容 15 | * @apiSuccess {String} msg 返回信息 16 | * 17 | * @apiSuccessExample {json} Success-Response: 18 | * HTTP/1.1 200 OK 19 | * { 20 | * "flag": 1, 21 | * "books": [ 22 | * { 23 | * "_id": "5816b415b06d1d32157790b1", 24 | * "title": "圣墟", 25 | * "author": "辰东", 26 | * "shortIntro": "在破败中崛起,在寂灭中复苏。沧海成尘,雷电枯竭,那一缕幽雾又一次临近大地,世间的枷锁被打开了,一个全新的世界就此揭开神秘的一角……", 27 | * "cover": "http://statics.zhuishushenqi.com/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F1228859%2F1228859_fac7917a960547eb953edf0b740cef3a.jpg%2F", 28 | * "site": "zhuishuvip", 29 | * "majorCate": "玄幻", 30 | * "minorCate": "东方玄幻", 31 | * "allowMonthly": false, 32 | * "banned": 0, 33 | * "latelyFollower": 283375, 34 | * "retentionRatio": "73.42" 35 | * } 36 | * ], 37 | * "msg": "OK" 38 | * } 39 | * 40 | * 41 | * @apiErrorExample Error-Response: 42 | * HTTP/1.1 404 Not Found 43 | * { "flag": 0, "msg": "rankingId有问题" } 44 | */ 45 | 46 | router.get('/', function(req, res, next) { 47 | // 请求追书最热榜 Top100 48 | request.get(`${common.API}/ranking/54d42d92321052167dfb75e3`, function (error, response, body) { 49 | if (error){ 50 | res.send(JSON.stringify({"flag": 0, "msg": "请求出错了..."})); 51 | } 52 | 53 | // 解析返回数据 54 | body = JSON.parse(body); 55 | 56 | if (body.ok){ 57 | // 取前20条,并添加图片url链接 58 | let books = body.ranking.books.slice(0, 19); 59 | books.forEach(element => { 60 | element.cover = common.PIC + element.cover; 61 | }); 62 | 63 | res.send(JSON.stringify({ "flag": 1, "books": books, "msg": "OK" })); 64 | }else{ 65 | res.send(JSON.stringify({ "flag": 0, "msg": "rankingId有问题" })); 66 | } 67 | }); 68 | }); 69 | 70 | module.exports = router; -------------------------------------------------------------------------------- /routes/chapter.js: -------------------------------------------------------------------------------- 1 | let express = require('express'); 2 | let request = require('request'); 3 | let common = require('../common/common.json'); // 引用公共文件 4 | let router = express.Router(); 5 | 6 | /** 7 | * @api {get} /chapter 获取小说文章列表 8 | * @apiVersion 1.0.0 9 | * @apiName 小说文章列表 10 | * @apiGroup chapter 11 | * 12 | * @apiParam {String} id 小说id 13 | * 14 | * @apiSuccess {Number} flag 是否获取到数据 1成功 0失败 15 | * @apiSuccess {Array} books 默认返回第二个源 16 | * @apiSuccess {String} msg 返回信息 17 | * 18 | * @apiSuccessExample {json} Success-Response: 19 | * HTTP/1.1 200 OK 20 | * { 21 | * "flag": 1, 22 | * "id": "57416370ccc94e4b41df80cc", 23 | * "chapters": [ 24 | * { 25 | * "title": "第1章 星空中的青铜巨棺", 26 | * "link": "http://book.my716.com/getBooks.aspx?method=content&bookId=42216&chapterFile=U_42216_201709300848186368_0866_1.txt", 27 | * "chapterCover": "", 28 | * "totalpage": 0, 29 | * "partsize": 0, 30 | * "order": 0, 31 | * "currency": 0, 32 | * "unreadble": false, 33 | * "isVip": false 34 | * "n": 0 35 | * } 36 | * ], 37 | * "msg": "OK" 38 | * } 39 | * 40 | * @apiErrorExample Error-Response: 41 | * HTTP/1.1 404 Not Found 42 | * { 43 | * "flag": 0, 44 | * "msg": "传入错误的ID..." 45 | * } 46 | */ 47 | 48 | router.get('/', function (req, res, next) { 49 | if (!req.query.id){ 50 | return res.send(JSON.stringify({ "flag": 0, "msg": "请传入ID..." })); 51 | } 52 | // req.query.id 编码转义 53 | let id = encodeURIComponent(req.query.id); 54 | request.get(`${common.API}/atoc/${id}?view=chapters`, function (err, response, body) { 55 | if (err) { 56 | return res.send(JSON.stringify({ "flag": 0, "msg": "请求出错了..." })); 57 | } 58 | 59 | if (body == "wrong param" || body.indexOf('Cannot GET /atoc/') != -1){ 60 | return res.send(JSON.stringify({ "flag": 0, "msg": "传入错误的ID..." })); 61 | }else{ 62 | // 解析返回的数据 63 | body = JSON.parse(body); 64 | if (body.chapters.length > 0) { 65 | body.chapters.forEach((el, index) => { 66 | el.n = index; 67 | }); 68 | return res.send(JSON.stringify({ "flag": 1,"id": body._id, "chapters": body.chapters, "msg": "OK" })); 69 | } 70 | } 71 | 72 | }); 73 | }); 74 | 75 | module.exports = router; -------------------------------------------------------------------------------- /routes/source.js: -------------------------------------------------------------------------------- 1 | let express = require('express'); 2 | let request = require('request'); 3 | let common = require('../common/common.json'); // 引用公共文件 4 | let router = express.Router(); 5 | 6 | /** 7 | * @api {get} /source 获取小说源正版和盗版 8 | * @apiVersion 1.0.0 9 | * @apiName 获取小说源 10 | * @apiGroup source 11 | * 12 | * @apiParam {String} id 小说id 13 | * @apiParam {Number} n(可选) 选择返回第几个源 14 | * 15 | * @apiSuccess {Number} flag 是否获取到数据 1成功 0失败 16 | * @apiSuccess {Array} books 默认返回第二个源 17 | * @apiSuccess {String} msg 返回信息 18 | * 19 | * @apiSuccessExample {json} Success-Response: 20 | * HTTP/1.1 200 OK 21 | * { 22 | * "flag": 1, 23 | * "books": { 24 | * "_id": "581825122ed01394526750b0", 25 | * "lastChapter": "第1068章 青州", 26 | * "link": "http://book.my716.com/getBooks.aspx?method=chapterList&bookId=1228859", 27 | * "source": "my176", 28 | * "name": "176小说", 29 | * "isCharge": false, 30 | * "chaptersCount": 1066, 31 | * "updated": "2018-05-26T17:53:40.233Z", 32 | * "starting": false, 33 | * "host": "book.my716.com" 34 | * }, 35 | * "msg": "OK" 36 | * } 37 | * 38 | * @apiErrorExample Error-Response: 39 | * HTTP/1.1 404 Not Found 40 | * { 41 | * "flag": 0, 42 | * "msg": "请传入ID..." 43 | * } 44 | */ 45 | 46 | router.get('/', function (req, res, next) { 47 | if (!req.query.id) { 48 | return res.send(JSON.stringify({ "flag": 0, "msg": "请传入ID..." })); 49 | } 50 | // req.query.id 编码转义 51 | let id = encodeURI(req.query.id); 52 | request.get(`${common.API}/atoc?view=summary&book=${id}`, function (err, response, body){ 53 | if(err){ 54 | return res.send(JSON.stringify({ "flag": 0, "msg": "请求出错了..." })); 55 | } 56 | 57 | // 解析返回的数据 58 | body = JSON.parse(body); 59 | // 判断是否返回内容 60 | if (body.ok === false){ 61 | return res.send(JSON.stringify({ "flag": 0, "msg": "没有获取到小说源,换个小说看吧" })); 62 | } 63 | 64 | // 第一个源是正版源,是收费加密的,所以默认选中第二个源 65 | let n = parseInt(req.query.n); 66 | if (isNaN(n) || n == 0){ 67 | n = 1; 68 | } 69 | 70 | // 判断n是否大于源数据的长度 71 | if (n >= body.length){ 72 | return res.send(JSON.stringify({ "flag": 0, "msg": "n的参数值不正确,没有那个源" })); 73 | }else{ 74 | return res.send(JSON.stringify({ "flag": 1, "books": body[n], "msg": "OK" })); 75 | } 76 | }); 77 | }); 78 | 79 | module.exports = router; -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | let express = require('express'); 2 | let path = require('path'); 3 | let cookieParser = require('cookie-parser'); 4 | let logger = require('morgan'); 5 | let fs = require('fs'); 6 | let FileStreamRotator = require('file-stream-rotator'); // 日志按时间分割模块 7 | 8 | let times = require('./middleware/times'); 9 | 10 | let indexRouter = require('./routes/index'); 11 | let searchRouter = require('./routes/search'); 12 | let sourceRouter = require('./routes/source'); 13 | let chapterRouter = require('./routes/chapter'); 14 | let articleRouter = require('./routes/article'); 15 | let rankingRouter = require('./routes/ranking'); 16 | let bookRouter = require('./routes/book'); 17 | 18 | let app = express(); 19 | let logDir = path.join(__dirname, 'log'); 20 | 21 | // 检查是否存在logDir这个目录没有则创建 22 | fs.existsSync(logDir) || fs.mkdirSync(logDir); 23 | 24 | // 日志按时间分割流 25 | let accessLogStream = FileStreamRotator.getStream({ 26 | date_format: 'YYYYMMDD', 27 | filename: path.join(logDir, 'access-%DATE%.log'), 28 | frequency: 'daily', 29 | verbose: false 30 | }); 31 | 32 | // 日志中间件 33 | app.use(logger('combined', { stream: accessLogStream })); 34 | app.use(express.json()); 35 | app.use(express.urlencoded({ extended: false })); 36 | app.use(cookieParser()); 37 | app.use(express.static(path.join(__dirname, 'public'))); 38 | 39 | // cors跨域 40 | app.all('*', function (req, res, next) { 41 | res.header("Access-Control-Allow-Origin", "*"); 42 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 43 | res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); 44 | res.header("X-Powered-By", ' 3.2.1') 45 | res.header("Content-Type", "application/json;charset=utf-8"); 46 | next() 47 | }); 48 | 49 | app.use(times(22,5)) 50 | 51 | // 路由中间件 52 | // 首页 53 | app.use('/index', indexRouter); 54 | // 搜索 55 | app.use('/search', searchRouter); 56 | // 小说源 57 | app.use('/source', sourceRouter); 58 | // 小说文章列表 59 | app.use('/chapter', chapterRouter); 60 | // 小说文章内容 61 | app.use('/article', articleRouter); 62 | // 排行榜 63 | app.use('/ranking', rankingRouter); 64 | // 小说信息 65 | app.use('/book', bookRouter); 66 | 67 | // catch 404 and forward to error handler 68 | app.use(function (req, res, next) { 69 | const err = new Error('Not Found'); 70 | err.status = 404; 71 | next(err); 72 | }); 73 | 74 | // error handler 75 | app.use(function (err, req, res, next) { 76 | // set locals, only providing error in development 77 | res.locals.message = err.message; 78 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 79 | 80 | // render the error page 81 | res.status(err.status || 500); 82 | res.render('error'); 83 | }); 84 | 85 | module.exports = app; 86 | -------------------------------------------------------------------------------- /routes/search.js: -------------------------------------------------------------------------------- 1 | let express = require('express'); 2 | let request = require('request'); 3 | let common = require('../common/common.json'); // 引用公共文件 4 | let router = express.Router(); 5 | 6 | /** 7 | * @api {get} /search 模糊搜索小说 8 | * @apiVersion 1.0.0 9 | * @apiName 模糊搜索小说 10 | * @apiGroup search 11 | * 12 | * @apiParam {String} query 搜索的关键字 13 | * 14 | * @apiSuccess {Number} flag 是否获取到数据 1成功 0失败 15 | * @apiSuccess {Array} books 返回搜索结果前40本小说 16 | * @apiSuccess {String} msg 返回信息 17 | * 18 | * @apiSuccessExample {json} Success-Response: 19 | * HTTP/1.1 200 OK 20 | * { 21 | * "flag": 1, 22 | * "books": [ 23 | * { 24 | * "_id": "50864bf69dacd30e3a000014", 25 | * "hasCp": true, 26 | * "title": "遮天", 27 | * "aliases": "", 28 | * "cat": "仙侠", 29 | * "author": "辰东", 30 | * "site": "zhuishuvip", 31 | * "cover": "http://statics.zhuishushenqi.com/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F42216%2F_42216_203892.jpg%2F", 32 | * "shortIntro": "冰冷与黑暗并存的宇宙深处,九具庞大的龙尸拉着一口青铜古棺,亘古长存。\n这是太空探测器在枯寂的宇宙中捕捉到的一幅极其震撼的画面。\n九龙拉棺,究竟是回到了上古,还是来到了星空的彼岸?\n一个浩大的仙侠世界,光怪陆离,神秘无尽。热血似火山沸腾,激情若瀚海汹涌,欲望如深渊无止境……\n登天路,踏歌行,弹指遮天。", 33 | * "lastChapter": "第一千八百二十二章 遮天大结局", 34 | * "retentionRatio": 64.3, 35 | * "banned": 0, 36 | * "allowMonthly": false, 37 | * "latelyFollower": 40292, 38 | * "wordCount": 6352116, 39 | * "contentType": "txt", 40 | * "superscript": "", 41 | * "sizetype": -1, 42 | * "highlight": { 43 | * "title": [ 44 | * "遮", 45 | * "天" 46 | * ] 47 | * } 48 | * } 49 | * ], 50 | * "msg": "OK" 51 | * } 52 | * 53 | * 54 | * @apiErrorExample Error-Response: 55 | * HTTP/1.1 404 Not Found 56 | * { 57 | * "flag": 0, 58 | * "msg": "请传入query参数" 59 | * } 60 | */ 61 | 62 | router.get('/', function(req, res, next) { 63 | // 判断query参数有没有传递过来 64 | if (req.query.query){ 65 | // req.query.query 编码转义 66 | let query = encodeURIComponent(req.query.query); 67 | request.get(`${common.API}/book/fuzzy-search?query=${query}`, function (error, response, body) { 68 | if (error){ 69 | return res.send(JSON.stringify({ "flag": 0, "msg": "请求出错了..." })); 70 | } 71 | 72 | // 解析返回数据 73 | body = JSON.parse(body); 74 | 75 | if (body.ok){ 76 | if (body.books.length == 0){ 77 | return res.send(JSON.stringify({ "flag": 0, "msg": "没有找到书籍,换个名字试试吧。" })); 78 | } 79 | 80 | // 取前40条,并添加图片url链接 81 | let books = body.books.slice(0, 39); 82 | books.forEach(element => { 83 | element.cover = common.PIC + element.cover; 84 | }); 85 | 86 | return res.send(JSON.stringify({ "flag": 1, "books": books, "msg": "OK" })); 87 | }else{ 88 | return res.send(JSON.stringify({ "flag": 0, "msg": "请求出错了..." })); 89 | } 90 | }); 91 | }else{ 92 | res.send(JSON.stringify({"flag": 0, "msg": "请传入query参数"})); 93 | } 94 | 95 | }); 96 | 97 | module.exports = router; -------------------------------------------------------------------------------- /routes/book.js: -------------------------------------------------------------------------------- 1 | let express = require('express'); 2 | let request = require('request'); 3 | let common = require('../common/common.json'); // 引用公共文件 4 | let router = express.Router(); 5 | 6 | /** 7 | * @api {get} /book:id 获取小说信息 8 | * @apiVersion 1.0.0 9 | * @apiName 获取小说信息 10 | * @apiGroup book 11 | * 12 | * @apiParam {String} id 书籍id 13 | * 14 | * @apiSuccess {Number} flag 是否获取到数据 1成功 0失败 15 | * @apiSuccess {JSON} book 书籍信息 16 | * @apiSuccess {String} msg 返回信息 17 | * 18 | * @apiSuccessExample {json} Success-Response: 19 | * HTTP/1.1 200 OK 20 | * { 21 | * "flag": 1, 22 | * "book": { 23 | * "_id": "5816b415b06d1d32157790b1", 24 | * "title": "圣墟", 25 | * "author": "辰东", 26 | * "longIntro": "在破败中崛起,在寂灭中复苏。沧海成尘,雷电枯竭,那一缕幽雾又一次临近大地,世间的枷锁被打开了,一个全新的世界就此揭开神秘的一角……", 27 | * "cover": "http://statics.zhuishushenqi.com/agent/http%3A%2F%2Fimg.1391.com%2Fapi%2Fv1%2Fbookcenter%2Fcover%2F1%2F1228859%2F1228859_d14f18e849b34420904ead54936e440a.jpg%2F", 28 | * "majorCate": "玄幻", 29 | * "minorCate": "东方玄幻", 30 | * "creater": "iPhone 5s (UK+Europe+Asis+China)", 31 | * "hiddenPackage": [], 32 | * "apptype": [ 33 | * 0, 34 | * 1, 35 | * 4 36 | * ], 37 | * "rating": { 38 | * "count": 40809, 39 | * "score": 8.879, 40 | * "isEffect": true 41 | * }, 42 | * "hasCopyright": true, 43 | * "buytype": 2, 44 | * "sizetype": -1, 45 | * "superscript": "", 46 | * "currency": 0, 47 | * "contentType": "txt", 48 | * "_le": false, 49 | * "allowMonthly": false, 50 | * "allowVoucher": true, 51 | * "allowBeanVoucher": false, 52 | * "hasCp": true, 53 | * "postCount": 70783, 54 | * "latelyFollower": 280426, 55 | * "followerCount": 0, 56 | * "wordCount": 4085239, 57 | * "serializeWordCount": 4781, 58 | * "retentionRatio": "73.98", 59 | * "updated": "2018-06-03T03:09:57.256Z", 60 | * "isSerial": true, 61 | * "chaptersCount": 1079, 62 | * "lastChapter": "第1081章 楚财大气粗", 63 | * "gender": [ 64 | * "male" 65 | * ], 66 | * "tags": [], 67 | * "advertRead": true, 68 | * "cat": "东方玄幻", 69 | * "donate": false, 70 | * "copyright": "阅文集团正版授权", 71 | * "_gg": false, 72 | * "discount": null, 73 | * "limit": false 74 | * }, 75 | * "msg": "OK" 76 | * } 77 | * 78 | * @apiErrorExample Error-Response: 79 | * HTTP/1.1 404 Not Found 80 | * { 81 | * "flag": 0, 82 | * "msg": "id没有传" 83 | * } 84 | */ 85 | 86 | router.get('/:id', function (req, res, next) { 87 | if (req.params.id) { 88 | // req.param.id 编码转义 89 | let id = encodeURIComponent(req.params.id); 90 | 91 | // 根据id获取榜单 92 | request.get(`${common.API}/book/${id}`, function (err, response, body) { 93 | if (err) { 94 | return res.send(JSON.stringify({ "flag": 0, "msg": "请求出错了..." })); 95 | } 96 | 97 | // 解析返回的数据 98 | body = JSON.parse(body); 99 | body.cover = common.PIC + body.cover; 100 | 101 | if (body._id) { 102 | return res.send(JSON.stringify({ "flag": 1, "book": body, "msg": "OK" })); 103 | } else { 104 | return res.send(JSON.stringify({ "flag": 0, "msg": "传入id错误" })); 105 | } 106 | }); 107 | } else { 108 | return res.send(JSON.stringify({ "flag": 0, "msg": "id没有传" })); 109 | } 110 | }); 111 | 112 | module.exports = router; -------------------------------------------------------------------------------- /routes/ranking.js: -------------------------------------------------------------------------------- 1 | let express = require('express'); 2 | let request = require('request'); 3 | let common = require('../common/common.json'); // 引用公共文件 4 | let router = express.Router(); 5 | 6 | /** 7 | * @api {get} /ranking:id 获取排行榜 8 | * @apiVersion 1.0.0 9 | * @apiName 获取排行榜 10 | * @apiGroup ranking 11 | * 12 | * @apiParam {String} id (可选)没有传参数就是获取全部榜单,否则根据排行榜id获取榜单 13 | * 14 | * @apiSuccess {Number} flag 是否获取到数据 1成功 0失败 15 | * @apiSuccess {Array} ranking 排行榜数据 16 | * @apiSuccess {String} msg 返回信息 17 | * 18 | * @apiSuccessExample {json} Success-Response: 19 | * HTTP/1.1 200 OK 20 | * { 21 | * "flag": 1, 22 | * "ranking": { 23 | * "male": [ 24 | * { 25 | * "_id": "54d42d92321052167dfb75e3", 26 | * "title": "追书最热榜 Top100", 27 | * "cover": "/ranking-cover/142319144267827", 28 | * "collapse": false, 29 | * "monthRank": "564d820bc319238a644fb408", 30 | * "totalRank": "564d8494fe996c25652644d2", 31 | * "shortTitle": "最热榜" 32 | * } 33 | * ], 34 | * "picture": [ 35 | * { 36 | * "_id": "5a322ef4fc84c2b8efaa8335", 37 | * "title": "人气榜", 38 | * "cover": "/ranking-cover/142319144267827", 39 | * "collapse": false, 40 | * "shortTitle": "人气榜" 41 | * } 42 | * ], 43 | * "epub": [ 44 | * { 45 | * "_id": "5a323096fc84c2b8efab2482", 46 | * "title": "热搜榜", 47 | * "cover": "/ranking-cover/142319144267827", 48 | * "collapse": false, 49 | * "shortTitle": "热搜榜" 50 | * } 51 | * ], 52 | * "female": [ 53 | * { 54 | * "_id": "54d43437d47d13ff21cad58b", 55 | * "title": "追书最热榜 Top100", 56 | * "cover": "/ranking-cover/142319314350435", 57 | * "collapse": false, 58 | * "monthRank": "564d853484665f97662d0810", 59 | * "totalRank": "564d85b6dd2bd1ec660ea8e2", 60 | * "shortTitle": "最热榜" 61 | * } 62 | * ] 63 | * }, 64 | * "msg": "OK" 65 | * } 66 | * 67 | * @apiErrorExample Error-Response: 68 | * HTTP/1.1 404 Not Found 69 | * { 70 | * "flag": 0, 71 | * "msg": "传入id错误" 72 | * } 73 | */ 74 | 75 | router.get('/', function (req, res, next) { 76 | // 获取全部榜单 77 | request.get(`${common.API}/ranking/gender`, function (err, response, body) { 78 | if (err) { 79 | return res.send(JSON.stringify({ "flag": 0, "msg": "请求出错了..." })); 80 | } 81 | 82 | // 解析返回的数据 83 | body = JSON.parse(body); 84 | 85 | if (body.ok) { 86 | let ranking = { 87 | male: body.male, 88 | picture: body.picture, 89 | epub: body.epub, 90 | female: body.female 91 | }; 92 | return res.send(JSON.stringify({ "flag": 1, "ranking": ranking, "msg": "OK" })); 93 | } else { 94 | return res.send(JSON.stringify({ "flag": 0, "msg": "出错了" })); 95 | } 96 | }); 97 | }); 98 | 99 | router.get('/:id', function (req, res, next) { 100 | if (req.params.id) { 101 | // req.param.id 编码转义 102 | let id = encodeURIComponent(req.params.id); 103 | 104 | // 根据id获取榜单 105 | request.get(`${common.API}/ranking/${id}`, function (err, response, body) { 106 | if (err) { 107 | return res.send(JSON.stringify({ "flag": 0, "msg": "请求出错了..." })); 108 | } 109 | 110 | // 解析返回的数据 111 | body = JSON.parse(body); 112 | 113 | body.ranking.books.forEach(element => { 114 | element.cover = common.PIC + element.cover; 115 | }); 116 | 117 | if (body.ok) { 118 | return res.send(JSON.stringify({ "flag": 1, "ranking": body.ranking, "msg": "OK" })); 119 | } else { 120 | return res.send(JSON.stringify({ "flag": 0, "msg": "传入id错误" })); 121 | } 122 | }); 123 | }else{ 124 | return res.send(JSON.stringify({ "flag": 0, "msg": "id没有传" })); 125 | } 126 | }); 127 | 128 | module.exports = router; -------------------------------------------------------------------------------- /routes/article.js: -------------------------------------------------------------------------------- 1 | let express = require('express'); 2 | let request = require('request'); 3 | let common = require('../common/common.json'); // 引用公共文件 4 | let router = express.Router(); 5 | 6 | /** 7 | * @api {get} /article 获取小说文章内容 8 | * @apiVersion 1.0.0 9 | * @apiName 小说文章内容 10 | * @apiGroup article 11 | * 12 | * @apiParam {String} link 小说link 13 | * @apiParam {Number} n (可选)小说的页数,默认0 14 | * @apiParam {String} id (可选)小说id,如果不传就没有上一章和下一章 15 | * 16 | * @apiSuccess {Number} flag 是否获取到数据 1成功 0失败 17 | * @apiSuccess {String} id 小说id 18 | * @apiSuccess {JSON} chapter 文章内容 19 | * @apiSuccess {String} prev 上一章 20 | * @apiSuccess {String} next 下一章 21 | * @apiSuccess {String} msg 返回信息 22 | * 23 | * @apiSuccessExample {json} Success-Response: 24 | * HTTP/1.1 200 OK 25 | * { 26 | * "flag": 1, 27 | * "id": "577b6c81ccb7bf00499d036c", 28 | * "chapter": { 29 | * "title": "第一章 星空中的青铜巨棺", 30 | * "body": "第一章星空中的青铜巨棺\n生命是世间最伟大的奇迹。\n四方上下曰宇。宇虽有实,而无定处可求。往古来今曰宙。宙虽有增长,不知其始之所至。\n浩瀚的宇宙,无垠的星空,许多科学家推测,地球可能是唯一的生命源地。\n人类其实很孤独。在苍茫的天宇中,虽然有亿万星辰,但是却很难寻到第二颗生命源星。\n遮天1\n不过人类从来没有放弃过探索,自上世纪以来已经『射』诸多太空探测器。\n旅行者二号是一艘无人外太空探测器,于一九七七年在美国肯尼迪航天中心『射』升空。\n它上面携带着一张主题为“向宇宙致意”的镀金唱片,里面包含一些流行音乐和用地球五十五种语言录制的问候辞,以冀有一天被可能存在的外星文明拦截和收到。\n从上世纪七十年代到现在,旅行者二号一直在孤独的旅行,在茫茫宇宙中它如一粒尘埃般渺小。\n同时代的外太空探测器,大多或已经生故障,或已经中断讯号联系,永远的消失在了枯寂的宇宙中。\n三十几年过去了,科技在不断展,人类已经研制出更加先进的外太空探测器,也许不久的将来对星空的探索会取得更进一步的展。\n但纵然如此,在相当长的时间内,新型外太空探测器依然无法追上旅行者二号的步伐。\n三十三年过去了,旅行者二号距离地球已经有一百四十亿公里。\n此时此刻,它已经达到第三宇宙度,轨道再也不能引导其飞返太阳系,成为了一艘星际太空船。\n黑暗与冰冷的宇宙中,星辰点点,犹如一颗颗晶莹的钻石镶嵌在黑幕上。\n旅行者二号太空探测器虽然正在极飞行,但是在幽冷与无垠的宇宙中却像是一只小小的蚁虫在黑暗的大地上缓缓爬行。\n三十多年过去后,就在今日这一刻,旅行者二号有了惊人的现!\n在枯寂的宇宙中,九具庞大的尸体静静的横在那里……\n二零一零年五月二十二日,美国宇航局接收到旅行者二号传送回的一组神秘的数据信息,经过艰难的破译与还原,他们看到了一幅不可思议的画面。\n在这一刻宇航局主监控室内所有人同时变『色』,最后如木雕泥塑般一动不动,他们震惊到了无以复加的地步!\n直至过了很久众人才回过神来,而后主监控室内一下子沸腾了。\n“上帝,我看到了什么?”\n“这怎么可能,无法相信!”\n…… 遮天1\n旅行者二号早已不受引导,只能单一的前进,传送回这组神秘的数据信息后,在那片漆黑的宇宙空间匆匆而过,驶向更加幽暗与深远的星域。\n由于那片星空太遥远,纵然有了重大现,捕捉到了一幅震撼『性』的画面,人类目前也无能为力。\n这组神秘信息并没有对外公布。而不久后,旅行者二号生了故障,中断了与地球的讯号传送。\n也许至此可以画上一个句号了,不过有时候事情往往会出乎人们的预料。\n无论是对星空的观测与探索,还是进行生命与物理的科学研究,空间站都具有得天独厚的优越环境。\n从一九七一年苏联先『射』载人空间站成功,到目前为止全世界已『射』了九个空间站。\n二零一零年六月十一日,此时此刻,绕地而行的国际空间站内,几名宇航员同时变了颜『色』,瞳孔急骤收缩。\n时至今日,神的存在,早已被否定。如果还有人继续信仰,那也只是因心灵空虚而寻找一份寄托而已。\n但是就在这一刻,几名宇航精英的思想受到了强烈的的冲击,他们看到了一幅不可思议的画面。\n在国际空间站外,冰冷与黑暗并存的宇宙中,九条庞然大物一动不动,仿佛亘古就已横在那里,让人感觉到无尽的苍凉与久远,那竟然是九具龙尸!\n与古代神话传说中的龙一般无二。\n每具龙尸都长达百米,犹如铁水浇铸而成,充满了震撼『性』的力感。\n九具龙尸皆是五爪黑龙,除却龙角晶莹剔透、紫光闪闪外,龙身通体呈黑『色』,乌光烁烁,鳞片在黑暗中闪烁着点点神秘的光华。\n龙,传说中的存在,与神并立,凌驾于自然规律之上。但是,科学展到现在,还有谁会相信龙真的存在?\n国际空间站内的几名宇航员,思想受到了强烈的冲击,眼前所见让他们感觉不可思议!\n枯寂的宇宙中,冰冷的龙尸似不可摧毁的钢铁长城,甚至能够感觉到尸身中所蕴含的恐怖力量。\n只是,它们已经失去了生气,永远的安息在了幽冷的宇宙空间中。\n“那是……”\n被深深震撼过后,几名宇航精英的瞳孔再次急骤收缩,他们看到了更为让人震惊的画面。\n九具龙尸都长达百米,在尾端皆绑缚着碗口粗的黑『色』铁索,连向九具龙尸身后那片黑暗的宇宙空间,在那里静静的悬着一口长达二十米的青铜棺椁。\n巨索千锤百炼,粗长而又坚固,点点乌光令它显得阴寒无比。\n青铜巨棺古朴无华,上面有一些模糊的古老图案,充满了岁月的沧桑感,也不知道在宇宙中漂浮多少年了。\n遮天1\n九龙拉棺!\n在这漆黑而又冰冷的宇宙中,九具龙尸与青铜巨棺被粗长的黑『色』铁索连在一起,显得极其震撼。\n在面对那不可思议的监控画面一阵失神后,几名宇航精英第一时间出了呼叫讯号。\n“呼叫地球……”\n※※※※※\n关于旅行者二号确实存在,上世纪七十年代在美国『射』升空,2o1o年四五月份与地球失去联系。\n辰东的新书开始上传,距离上本老书结束已经有了一段相当长的空白期,现在回来了,请各位书友多多支持下,感谢!\n现在需要登录起点帐号,点击才有效,不然点击不计算在内,请给位兄弟姐妹们辛苦下。\n新书需要点击、收藏、推荐票,请书友们支持下新书。\n晚上会接着更新的。" 31 | * }, 32 | * "prev": "http://www.biquge.la/book/5210/3142585.html", 33 | * "next": "http://www.biquge.la/book/5210/3142587.html", 34 | * "msg": "OK" 35 | * } 36 | * 37 | * @apiErrorExample Error-Response: 38 | * HTTP/1.1 404 Not Found 39 | * { 40 | * "flag": 0, 41 | * "msg": "传入的页码过大" 42 | * } 43 | */ 44 | 45 | router.get('/', function (req, res, next) { 46 | if (!req.query.link) { 47 | res.send(JSON.stringify({ "flag": 0, "msg": "请传入link..." })); 48 | } 49 | // req.query.link 编码转义 50 | let link = encodeURIComponent(req.query.link); 51 | request.get(`${common.CHAPTER}/chapter/${link}`, function (err, response, body) { 52 | if (err) { 53 | res.send(JSON.stringify({ "flag": 0, "msg": "请求出错了..." })); 54 | } 55 | 56 | // 解析返回的数据 57 | body = JSON.parse(body); 58 | 59 | if (body.ok){ 60 | // 再次请求列表页获取上一页和下一页 61 | if(req.query.id){ 62 | // req.query.id 编码转义 63 | let id = encodeURIComponent(req.query.id); 64 | let n = parseInt(req.query.n); 65 | if (isNaN(n)){ 66 | n = 0; 67 | } 68 | 69 | request.get(`${common.API}/atoc/${id}?view=chapters`, function (err, response, body2) { 70 | if (err) { 71 | res.send(JSON.stringify({ "flag": 0, "msg": "请求出错了..." })); 72 | } 73 | 74 | if (body2 == "wrong param"){ 75 | res.send(JSON.stringify({ "flag": 0, "msg": "传入错误的ID..." })); 76 | }else{ 77 | // 解析返回的数据 78 | body2 = JSON.parse(body2); 79 | // 检查页码是否超过小说的章节数 80 | if(n > body2.chapters.length - 1){ 81 | res.send(JSON.stringify({ "flag": 0, "msg": "传入的页码过大" })); 82 | }else{ 83 | // 如果有上一页或者下一页就返回link否则返回false 84 | let prev,next; 85 | body2.chapters[n - 1] ? prev = body2.chapters[n - 1].link : prev = false; 86 | body2.chapters[n + 1] ? next = body2.chapters[n + 1].link : next = false; 87 | body.chapter.body += '\\n'; 88 | if (body2.chapters.length > 0) { 89 | res.send(JSON.stringify({ "flag": 1,"id": id, "chapter": body.chapter, "prev": prev,"next": next, "msg": "OK" })); 90 | } 91 | } 92 | } 93 | }); 94 | }else{ 95 | body.chapter.body += '\n'; 96 | res.send(JSON.stringify({ "flag": 1, "chapter": body.chapter, "msg": "OK" })); 97 | } 98 | 99 | }else{ 100 | res.send(JSON.stringify({ "flag": 0, "msg": "传入link有错误" })); 101 | } 102 | }); 103 | }); 104 | 105 | module.exports = router; --------------------------------------------------------------------------------