├── wyfReader
├── .watchmanconfig
├── .gitattributes
├── app.json
├── app
│ ├── images
│ │ ├── 2.png
│ │ ├── up.png
│ │ ├── back.png
│ │ ├── clock.png
│ │ ├── down.png
│ │ ├── house.png
│ │ ├── test.jpg
│ │ ├── person.png
│ │ ├── search.png
│ │ ├── search_1.png
│ │ ├── tabbar1.png
│ │ └── directory.png
│ ├── services
│ │ ├── auth.js
│ │ └── app.js
│ ├── utils
│ │ ├── index.js
│ │ ├── setAuthorizationToken.js
│ │ ├── dva.js
│ │ ├── request.js
│ │ └── utils.js
│ ├── libs
│ │ └── request.js
│ ├── index.js
│ ├── models
│ │ ├── router.js
│ │ ├── reader.js
│ │ └── app.js
│ ├── containers
│ │ ├── Account.js
│ │ ├── Directory.js
│ │ ├── Register.js
│ │ ├── Login.js
│ │ ├── Detail.js
│ │ ├── Search.js
│ │ └── Home.js
│ └── router.js
├── android
│ ├── app
│ │ ├── src
│ │ │ └── main
│ │ │ │ ├── res
│ │ │ │ ├── values
│ │ │ │ │ ├── strings.xml
│ │ │ │ │ └── styles.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ └── mipmap-xxhdpi
│ │ │ │ │ └── ic_launcher.png
│ │ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── wyfreader
│ │ │ │ │ ├── MainActivity.java
│ │ │ │ │ └── MainApplication.java
│ │ │ │ └── AndroidManifest.xml
│ │ ├── BUCK
│ │ ├── proguard-rules.pro
│ │ └── build.gradle
│ ├── gradle
│ │ └── wrapper
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ ├── keystores
│ │ ├── debug.keystore.properties
│ │ └── BUCK
│ ├── settings.gradle
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradlew.bat
│ └── gradlew
├── ios
│ ├── wyfReader
│ │ ├── Images.xcassets
│ │ │ ├── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ └── Contents.json
│ │ ├── AppDelegate.h
│ │ ├── main.m
│ │ ├── AppDelegate.m
│ │ ├── Info.plist
│ │ └── Base.lproj
│ │ │ └── LaunchScreen.xib
│ ├── wyfReaderTests
│ │ ├── Info.plist
│ │ └── wyfReaderTests.m
│ ├── wyfReader-tvOSTests
│ │ └── Info.plist
│ ├── wyfReader-tvOS
│ │ └── Info.plist
│ └── wyfReader.xcodeproj
│ │ └── xcshareddata
│ │ └── xcschemes
│ │ ├── wyfReader.xcscheme
│ │ └── wyfReader-tvOS.xcscheme
├── .buckconfig
├── .babelrc
├── index.js
├── jsconfig.json
├── .editorconfig
├── __tests__
│ └── App.js
├── .gitignore
├── .eslintrc
├── App.js
├── package.json
└── .flowconfig
├── wu-server
├── .rsync-exclude
├── .babelrc
├── index.js
├── config
│ ├── env
│ │ ├── common.js
│ │ ├── test.js
│ │ ├── development.js
│ │ └── production.js
│ ├── index.js
│ └── passport.js
├── .eslintrc.json
├── nginx.conf
├── src
│ ├── middleware
│ │ ├── index.js
│ │ └── validators.js
│ ├── modules
│ │ ├── auth
│ │ │ ├── router.js
│ │ │ └── controller.js
│ │ ├── test
│ │ │ ├── router.js
│ │ │ └── controller.js
│ │ ├── users
│ │ │ ├── router.js
│ │ │ └── controller.js
│ │ ├── chapters.js
│ │ │ ├── router.js
│ │ │ └── controller.js
│ │ ├── bookshelves.js
│ │ │ ├── router.js
│ │ │ └── controller.js
│ │ ├── novels.js
│ │ │ ├── router.js
│ │ │ └── controller.js
│ │ └── index.js
│ ├── utils
│ │ ├── auth.js
│ │ ├── schedule
│ │ │ └── updateChapter.js
│ │ ├── redis.js
│ │ ├── handle
│ │ │ └── index.js
│ │ ├── updateNovel.js
│ │ └── crawler.js
│ └── models
│ │ ├── novels.js
│ │ ├── bookshelfs.js
│ │ ├── chapters.js
│ │ └── users.js
├── .vscode
│ └── launch.json
├── README.md
├── Dockerfile
├── release.sh
├── .editorconfig
├── bin
│ └── server.js
└── package.json
├── LICENSE
├── .gitignore
└── README.md
/wyfReader/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/wyfReader/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj -text
2 |
--------------------------------------------------------------------------------
/wu-server/.rsync-exclude:
--------------------------------------------------------------------------------
1 | .git/*
2 | logs/*
3 | node_modules/*
4 | npm-*
5 | coverage
--------------------------------------------------------------------------------
/wyfReader/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wyfReader",
3 | "displayName": "wyfReader"
4 | }
--------------------------------------------------------------------------------
/wu-server/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015-node5",
4 | "stage-0"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/wyfReader/app/images/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyafeiJS/Reader/HEAD/wyfReader/app/images/2.png
--------------------------------------------------------------------------------
/wyfReader/app/images/up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyafeiJS/Reader/HEAD/wyfReader/app/images/up.png
--------------------------------------------------------------------------------
/wyfReader/app/images/back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyafeiJS/Reader/HEAD/wyfReader/app/images/back.png
--------------------------------------------------------------------------------
/wyfReader/app/images/clock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyafeiJS/Reader/HEAD/wyfReader/app/images/clock.png
--------------------------------------------------------------------------------
/wyfReader/app/images/down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyafeiJS/Reader/HEAD/wyfReader/app/images/down.png
--------------------------------------------------------------------------------
/wyfReader/app/images/house.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyafeiJS/Reader/HEAD/wyfReader/app/images/house.png
--------------------------------------------------------------------------------
/wyfReader/app/images/test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyafeiJS/Reader/HEAD/wyfReader/app/images/test.jpg
--------------------------------------------------------------------------------
/wu-server/index.js:
--------------------------------------------------------------------------------
1 | require('babel-core/register')()
2 | require('babel-polyfill')
3 | require('./bin/server.js')
4 |
--------------------------------------------------------------------------------
/wyfReader/app/images/person.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyafeiJS/Reader/HEAD/wyfReader/app/images/person.png
--------------------------------------------------------------------------------
/wyfReader/app/images/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyafeiJS/Reader/HEAD/wyfReader/app/images/search.png
--------------------------------------------------------------------------------
/wyfReader/app/images/search_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyafeiJS/Reader/HEAD/wyfReader/app/images/search_1.png
--------------------------------------------------------------------------------
/wyfReader/app/images/tabbar1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyafeiJS/Reader/HEAD/wyfReader/app/images/tabbar1.png
--------------------------------------------------------------------------------
/wyfReader/app/images/directory.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyafeiJS/Reader/HEAD/wyfReader/app/images/directory.png
--------------------------------------------------------------------------------
/wu-server/config/env/common.js:
--------------------------------------------------------------------------------
1 | export default {
2 | port: process.env.PORT || 5000,
3 | crawlerUrl: "https://www.qu.la"
4 | }
5 |
--------------------------------------------------------------------------------
/wyfReader/android/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | wyfReader
3 |
4 |
--------------------------------------------------------------------------------
/wyfReader/ios/wyfReader/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/wyfReader/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyafeiJS/Reader/HEAD/wyfReader/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/wyfReader/.buckconfig:
--------------------------------------------------------------------------------
1 |
2 | [android]
3 | target = Google Inc.:Google APIs:23
4 |
5 | [maven_repositories]
6 | central = https://repo1.maven.org/maven2
7 |
--------------------------------------------------------------------------------
/wyfReader/app/services/auth.js:
--------------------------------------------------------------------------------
1 | import { delay } from '../utils'
2 |
3 | export const login = async () => {
4 | await delay(2000)
5 | return true
6 | }
7 |
--------------------------------------------------------------------------------
/wu-server/config/env/test.js:
--------------------------------------------------------------------------------
1 | export default {
2 | session: 'secret-boilerplate-token',
3 | token: 'secret-jwt-token',
4 | database: 'mongodb://localhost:27017/wu'
5 | }
6 |
--------------------------------------------------------------------------------
/wyfReader/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyafeiJS/Reader/HEAD/wyfReader/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/wyfReader/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyafeiJS/Reader/HEAD/wyfReader/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/wyfReader/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyafeiJS/Reader/HEAD/wyfReader/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/wyfReader/android/keystores/debug.keystore.properties:
--------------------------------------------------------------------------------
1 | key.store=debug.keystore
2 | key.alias=androiddebugkey
3 | key.store.password=android
4 | key.alias.password=android
5 |
--------------------------------------------------------------------------------
/wyfReader/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react-native"],
3 | "plugins": [
4 | "transform-decorators-legacy",
5 | ["import", { "libraryName": "antd-mobile" }]
6 | ]
7 | }
--------------------------------------------------------------------------------
/wyfReader/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wuyafeiJS/Reader/HEAD/wyfReader/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/wyfReader/index.js:
--------------------------------------------------------------------------------
1 | // import { AppRegistry } from 'react-native'
2 | // import App from './App'
3 |
4 | // AppRegistry.registerComponent('wyfReader', () => App)
5 | import './app/index'
--------------------------------------------------------------------------------
/wyfReader/android/keystores/BUCK:
--------------------------------------------------------------------------------
1 | keystore(
2 | name = "debug",
3 | properties = "debug.keystore.properties",
4 | store = "debug.keystore",
5 | visibility = [
6 | "PUBLIC",
7 | ],
8 | )
9 |
--------------------------------------------------------------------------------
/wu-server/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "standard",
4 | "env": {
5 | "node": true,
6 | "mocha": true
7 | },
8 | "rules": {
9 | "eqeqeq": 0
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/wu-server/config/index.js:
--------------------------------------------------------------------------------
1 | import common from './env/common'
2 |
3 | const env = process.env.NODE_ENV || 'development'
4 | const config = require(`./env/${env}`).default
5 |
6 | export default Object.assign({}, common, config)
7 |
--------------------------------------------------------------------------------
/wyfReader/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "allowSyntheticDefaultImports": true,
5 | "experimentalDecorators": true
6 | },
7 | "exclude": [
8 | "node_modules"
9 | ]
10 | }
--------------------------------------------------------------------------------
/wyfReader/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'wyfReader'
2 | include ':react-native-svg'
3 | project(':react-native-svg').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-svg/android')
4 |
5 | include ':app'
6 |
--------------------------------------------------------------------------------
/wyfReader/app/utils/index.js:
--------------------------------------------------------------------------------
1 | export { NavigationActions } from 'react-navigation'
2 |
3 | export const delay = time => new Promise(resolve => setTimeout(resolve, time))
4 |
5 | export const createAction = type => payload => ({ type, payload })
6 |
--------------------------------------------------------------------------------
/wu-server/config/env/development.js:
--------------------------------------------------------------------------------
1 | export default {
2 | session: 'secret-boilerplate-token',
3 | token: 'secret-jwt-token',
4 | database: 'mongodb://120.79.161.225:27017/wu',
5 | redis: {
6 | host: 'localhost',
7 | port: 6379,
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/wu-server/config/env/production.js:
--------------------------------------------------------------------------------
1 | export default {
2 | session: 'secret-boilerplate-token',
3 | token: 'secret-jwt-token',
4 | database: 'mongodb://120.79.161.225:27017/wu',
5 | redis: {
6 | host: 'localhost',
7 | port: 6379,
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/wyfReader/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/wu-server/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | location / {
4 | root /opt/html;
5 | index index.html index.html;
6 | }
7 | location /server/ {
8 | expires -1;
9 | add_header Cache-Control no-cache,no-store;
10 | proxy_pass http://120.79.161.255:8080/;
11 | }
12 | }
--------------------------------------------------------------------------------
/wyfReader/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Apr 26 14:34:44 CST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/wu-server/src/middleware/index.js:
--------------------------------------------------------------------------------
1 | export function errorMiddleware () {
2 | return async (ctx, next) => {
3 | try {
4 | await next()
5 | } catch (err) {
6 | ctx.status = err.status || 500
7 | ctx.body = err.message
8 | ctx.app.emit('error', err, ctx)
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/wyfReader/app/utils/setAuthorizationToken.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 |
3 | export default function setAuthorizationToken(token) {
4 | if (token) {
5 | axios.defaults.headers.common['Authorization'] = `Bearer ${token}`
6 | } else {
7 | delete axios.defaults.headers.common['Authorization']
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/wyfReader/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 2
8 | indent_style = space
9 | insert_final_newline = true
10 | max_line_length = 80
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | max_line_length = 0
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/wyfReader/__tests__/App.js:
--------------------------------------------------------------------------------
1 | import 'react-native';
2 | import React from 'react';
3 | import App from '../App';
4 |
5 | // Note: test renderer must be required after react-native.
6 | import renderer from 'react-test-renderer';
7 |
8 | it('renders correctly', () => {
9 | const tree = renderer.create(
10 |
11 | );
12 | });
13 |
--------------------------------------------------------------------------------
/wu-server/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // 使用 IntelliSense 了解相关属性。
3 | // 悬停以查看现有属性的描述。
4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "Launch Program",
11 | "program": "${workspaceFolder}/index.js"
12 | }
13 | ]
14 | }
--------------------------------------------------------------------------------
/wu-server/src/modules/auth/router.js:
--------------------------------------------------------------------------------
1 | import * as auth from './controller'
2 |
3 | export const baseUrl = '/auth'
4 |
5 | export default [
6 | {
7 | method: 'POST',
8 | route: '/',
9 | handlers: [
10 | auth.authUser
11 | ]
12 | },
13 | {
14 | method: 'POST',
15 | route: '/test',
16 | handlers: [
17 | auth.testUser
18 | ]
19 | }
20 | ]
21 |
--------------------------------------------------------------------------------
/wu-server/src/modules/test/router.js:
--------------------------------------------------------------------------------
1 | import * as test from './controller'
2 |
3 | export const baseUrl = '/test'
4 |
5 | export default [
6 | {
7 | method: 'GET',
8 | route: '/',
9 | handlers: [
10 | test.getChapters
11 | ]
12 | },
13 | {
14 | method: 'GET',
15 | route: '/img',
16 | handlers: [
17 | test.getImgs
18 | ]
19 | }
20 | ]
21 |
--------------------------------------------------------------------------------
/wu-server/README.md:
--------------------------------------------------------------------------------
1 | ## 简介
2 | 基于koa2的api服务器,数据库mongoodb。
3 |
4 | ## 安装
5 | 请先确定安装了 mongoodb。
6 | ```bash
7 | git clone https://github.com/dlyt/YCool_Server.git
8 | npm install
9 | ```
10 |
11 | ## 开发
12 | ```bash
13 | npm run dev
14 | ```
15 |
16 | ## API文档
17 |
18 | ```bash
19 | npm run docs
20 | ```
21 |
22 | 运行项目后查看: http://localhost:5000/docs/
23 |
24 | ## License
25 |
26 | 仅供学习使用
27 |
--------------------------------------------------------------------------------
/wu-server/src/utils/auth.js:
--------------------------------------------------------------------------------
1 | export function getToken (ctx) {
2 | const header = ctx.request.header.authorization
3 | if (!header) {
4 | return null
5 | }
6 |
7 | const parts = header.split(' ')
8 | if (parts.length !== 2) {
9 | return null
10 | }
11 | const scheme = parts[0]
12 | const token = parts[1]
13 | if (/^Bearer$/i.test(scheme)) {
14 | return token
15 | }
16 | return null
17 | }
18 |
--------------------------------------------------------------------------------
/wyfReader/android/app/src/main/java/com/wyfreader/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.wyfreader;
2 |
3 | import com.facebook.react.ReactActivity;
4 |
5 | public class MainActivity extends ReactActivity {
6 |
7 | /**
8 | * Returns the name of the main component registered from JavaScript.
9 | * This is used to schedule rendering of the component.
10 | */
11 | @Override
12 | protected String getMainComponentName() {
13 | return "wyfReader";
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/wu-server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM hub.c.163.com/library/node:8.4-slim
2 | MAINTAINER wuyafei
3 |
4 | RUN mkdir -p /opt/reader-server
5 | ADD . /opt/reader-server
6 |
7 | COPY ./ /opt/reader-server
8 | WORKDIR /opt/reader-server
9 | # RUN npm config set registry http://10.2.144.44:8081/repository/npm && \
10 | # NODEJS_ORG_MIRROR=http://10.2.144.44:8081/repository/node npm i --production --verbose
11 | RUN npm i
12 |
13 | EXPOSE 5000
14 |
15 | CMD NODE_ENV=production node index.js
16 |
--------------------------------------------------------------------------------
/wyfReader/ios/wyfReader/AppDelegate.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | */
9 |
10 | #import
11 |
12 | @interface AppDelegate : UIResponder
13 |
14 | @property (nonatomic, strong) UIWindow *window;
15 |
16 | @end
17 |
--------------------------------------------------------------------------------
/wu-server/src/modules/users/router.js:
--------------------------------------------------------------------------------
1 | import { ensureUser } from '../../middleware/validators'
2 | import * as user from './controller'
3 |
4 | export const baseUrl = '/users'
5 |
6 | export default [
7 | {
8 | method: 'POST',
9 | route: '/tourists',
10 | handlers: [
11 | user.createTourist
12 | ]
13 | },
14 | {
15 | method: 'POST',
16 | route: '/register',
17 | handlers: [
18 | user.register
19 | ]
20 | },
21 | {
22 | method: 'GET',
23 | route: '/captcha',
24 | handlers: [
25 | user.getCaptcha
26 | ]
27 | }
28 | ]
29 |
30 |
--------------------------------------------------------------------------------
/wyfReader/ios/wyfReader/main.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | */
9 |
10 | #import
11 |
12 | #import "AppDelegate.h"
13 |
14 | int main(int argc, char * argv[]) {
15 | @autoreleasepool {
16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/wu-server/src/models/novels.js:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose'
2 |
3 | const Schema = mongoose.Schema
4 | const ObjectId = Schema.Types.ObjectId
5 |
6 | const Novel = new mongoose.Schema({
7 | type: { type: String, default: 'Normal' },
8 | name: { type: String , unique: true},
9 | url: { type: String },
10 | status: { type: Number },
11 | author: { type: String },
12 | introduction: { type: String },
13 | img: { type: String, default: '' },
14 | updateTime: { type: String },
15 | lastChapterTitle: { type: String },
16 | countChapter: { type: Number },
17 | })
18 |
19 |
20 | export default mongoose.model('novel', Novel)
21 |
--------------------------------------------------------------------------------
/wyfReader/app/utils/dva.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { create } from 'dva-core'
3 | import { Provider, connect } from 'react-redux'
4 |
5 | export { connect }
6 |
7 | export default function(options) {
8 | const app = create(options)
9 | if (!global.registered) options.models.forEach(model => app.model(model))
10 | global.registered = true
11 | app.start()
12 | // eslint-disable-next-line no-underscore-dangle
13 | const store = app._store
14 |
15 | app.start = container => () =>
16 |
17 | {container}
18 |
19 |
20 | app.getStore = () => store
21 |
22 | return app
23 | }
24 |
--------------------------------------------------------------------------------
/wu-server/src/middleware/validators.js:
--------------------------------------------------------------------------------
1 | import User from '../models/users'
2 | import config from '../../config'
3 | import { getToken } from '../utils/auth'
4 | import { verify } from 'jsonwebtoken'
5 |
6 | export async function ensureUser (ctx, next) {
7 | const token = getToken(ctx)
8 |
9 | if (!token) {
10 | ctx.throw(401)
11 | }
12 |
13 | let decoded = null
14 | try {
15 | decoded = verify(token, config.token)
16 | } catch (err) {
17 | ctx.throw(401)
18 | }
19 | // 缓存userid
20 | ctx.state.user = await User.findById(decoded.id, '-password')
21 | if (!ctx.state.user) {
22 | ctx.throw(401)
23 | }
24 |
25 | return next()
26 | }
27 |
--------------------------------------------------------------------------------
/wyfReader/app/libs/request.js:
--------------------------------------------------------------------------------
1 | let request = {};
2 | const prefix = 'http://120.79.161.225/server'
3 | request.get = (url) => {
4 | let options = {
5 | method: 'GET',
6 | header: {
7 | 'Content-Type': 'application/json'
8 | }
9 | }
10 | let link = `${prefix}${url}`
11 | return new Promise((resolve, reject) => {
12 | fetch(link, options).then(res => {
13 | if (res.ok) {
14 | return res.json()
15 | }
16 | return reject({ status: res.status })
17 | }).then(res => {
18 | resolve(res)
19 | }).catch((err) => {
20 | reject({status: -1, msg: err.message})
21 | })
22 | })
23 | }
24 | // request.post = ()
25 |
26 | export default request
--------------------------------------------------------------------------------
/wu-server/src/modules/test/controller.js:
--------------------------------------------------------------------------------
1 | import Chapter from '../../models/chapters'
2 | import Novel from '../../models/novels'
3 | import * as Crawler from '../../utils/crawler'
4 | import * as UpdateNovel from '../../utils/updateNovel'
5 |
6 |
7 | export async function getChapters (ctx) {
8 | await UpdateNovel.update()
9 | ctx.body = {
10 | success: true
11 | }
12 | }
13 |
14 | export async function getImgs (ctx) {
15 | const type = ctx.query.type
16 | // const data = await Crawler.request('http://112.74.34.241:3000/meizi/random?type=%E5%8F%B0%E6%B9%BE')
17 | // const url = JSON.parse(data).url
18 |
19 | ctx.body = {
20 | // url: url
21 | url: "http://og1m0yoqf.bkt.clouddn.com/banner_index.png"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/wu-server/src/utils/schedule/updateChapter.js:
--------------------------------------------------------------------------------
1 | let schedule = require('node-schedule')
2 | let cucurrentRule = new schedule.RecurrenceRule()
3 | import * as UpdateNovel from '../updateNovel'
4 | // let scheduleTime = [10, 51, 81] // 检查频率
5 | // cucurrentRule.dayOfWeek = 0
6 | cucurrentRule.hour = 24
7 | cucurrentRule.minute = 0
8 | // cucurrentRule.second = scheduleTime
9 | console.log('===scheduler start ========.')
10 | schedule.scheduleJob(cucurrentRule, async function () {
11 | try {
12 | console.info('===update started at: ' + new Date())
13 | await UpdateNovel.update()
14 | console.log('====down at: ' + new Date())
15 | } catch (error) {
16 | console.error('process error: ' + error)
17 | } finally {
18 | console.info('===ended at: ' + new Date())
19 | }
20 | })
21 |
--------------------------------------------------------------------------------
/wu-server/release.sh:
--------------------------------------------------------------------------------
1 | BUILD_TIME=`date "+%Y%m%d%H%M"`
2 | SERVER_HOST="root@120.79.161.225"
3 | SERVER_PATH="/opt/wuReader"
4 | IMAGE_NAME="hub.c.163.com/wuyafeijs/reader-server:$BUILD_TIME"
5 |
6 | rsync -cavzP --delete-after ./ --exclude-from='.rsync-exclude' root@120.79.161.225:$SERVER_PATH
7 | ssh $SERVER_HOST "\
8 | cd $SERVER_PATH; \
9 | docker kill reader-server; \
10 | docker rm reader-server; \
11 | echo "清理过时的镜像"; \
12 | docker images | awk '/^hub.c.163.com\/wuyafeijs\/reader-server[ ]+/ { print \$3 }' | xargs docker rmi ; \
13 | docker build -t $IMAGE_NAME .;\
14 | docker run --name reader-server -p 8080:5000 -d --restart=always $IMAGE_NAME; \
15 | exit; \
16 | "
17 | echo "\033[40;32m\n"
18 | echo "Image: $IMAGE_NAME"; \
19 | # echo "Image run success."
20 | echo "\033[0m"
21 |
--------------------------------------------------------------------------------
/wu-server/src/modules/chapters.js/router.js:
--------------------------------------------------------------------------------
1 | import { ensureUser } from '../../middleware/validators'
2 | import * as chapter from './controller'
3 |
4 | export const baseUrl = '/chapters'
5 |
6 | export default [
7 | {
8 | method: 'POST',
9 | route: '/',
10 | handlers: [
11 | ensureUser,
12 | chapter.getChapterInfo
13 | ]
14 | },
15 | {
16 | method: 'GET',
17 | route: '/firstRender',
18 | handlers: [
19 | ensureUser,
20 | chapter.getFirstRenderChapter
21 | ]
22 | },
23 | {
24 | method: 'GET',
25 | route: '/next/:id',
26 | handlers: [
27 | chapter.getNextChapterInfo
28 | ]
29 | },
30 | {
31 | method: 'GET',
32 | route: '/last/:id',
33 | handlers: [
34 | chapter.getLastChapterInfo
35 | ]
36 | },
37 | ]
38 |
--------------------------------------------------------------------------------
/wyfReader/ios/wyfReader/Images.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | }
33 | ],
34 | "info" : {
35 | "version" : 1,
36 | "author" : "xcode"
37 | }
38 | }
--------------------------------------------------------------------------------
/wu-server/src/modules/bookshelves.js/router.js:
--------------------------------------------------------------------------------
1 | import { ensureUser } from '../../middleware/validators'
2 | import * as bookshelf from './controller'
3 |
4 | export const baseUrl = '/bookshelfs'
5 |
6 | export default [
7 | {
8 | method: 'GET',
9 | route: '/',
10 | handlers: [
11 | ensureUser,
12 | bookshelf.getBookshelf
13 | ]
14 | },
15 | {
16 | method: 'POST',
17 | route: '/order',
18 | handlers: [
19 | ensureUser,
20 | bookshelf.orderNovel
21 | ]
22 | },
23 | {
24 | method: 'POST',
25 | route: '/delete',
26 | handlers: [
27 | bookshelf.delectNovel
28 | ]
29 | },
30 | {
31 | method: 'POST',
32 | route: '/change',
33 | handlers: [
34 | ensureUser,
35 | bookshelf.changeBookshelf
36 | ]
37 | }
38 | ]
39 |
--------------------------------------------------------------------------------
/wu-server/src/models/bookshelfs.js:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose'
2 |
3 | const Schema = mongoose.Schema
4 | const ObjectId = Schema.Types.ObjectId
5 |
6 | const Bookshelf = new mongoose.Schema({
7 | user: { type: ObjectId, ref: 'user' },
8 | novel: { type: ObjectId, ref: 'novel'},
9 | chapter: { type: ObjectId, ref: 'chapter'},
10 | number: { type: Number, default: 0},
11 | progress: { type: Number, default: 0}
12 | })
13 |
14 | Bookshelf.statics = {
15 | getList: function (id){
16 | return this
17 | .find({user: id})
18 | .populate('novel')
19 | .populate('chapter')
20 | .exec()
21 | },
22 | findByUserAndNovelId: function (options){
23 | return this
24 | .findOne({user: options.userId, novel: options.novelId})
25 | .exec()
26 | }
27 | }
28 |
29 | export default mongoose.model('bookshelf', Bookshelf)
30 |
--------------------------------------------------------------------------------
/wyfReader/ios/wyfReaderTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/wyfReader/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | maven {
7 | url 'https://maven.google.com'
8 | }
9 | google()
10 | }
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:3.0.1'
13 |
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | mavenLocal()
22 | jcenter()
23 | maven {
24 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
25 | url "$rootDir/../node_modules/react-native/android"
26 | }
27 | google()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/wyfReader/ios/wyfReader-tvOSTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/wu-server/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | # A special property that should be specified at the top of the file outside of
4 | # any sections. Set to true to stop .editor config file search on current file
5 | root = true
6 |
7 | [*]
8 | # Indentation style
9 | # Possible values - tab, space
10 | indent_style = space
11 |
12 | # Indentation size in single-spaced characters
13 | # Possible values - an integer, tab
14 | indent_size = 2
15 |
16 | # Line ending file format
17 | # Possible values - lf, crlf, cr
18 | end_of_line = lf
19 |
20 | # File character encoding
21 | # Possible values - latin1, utf-8, utf-16be, utf-16le
22 | charset = utf-8
23 |
24 | # Denotes whether to trim whitespace at the end of lines
25 | # Possible values - true, false
26 | trim_trailing_whitespace = true
27 |
28 | # Denotes whether file should end with a newline
29 | # Possible values - true, false
30 | insert_final_newline = true
31 |
--------------------------------------------------------------------------------
/wu-server/src/utils/redis.js:
--------------------------------------------------------------------------------
1 | // import Config from '../../config'
2 |
3 |
4 | // const redis = require('redis').createClient(Config.redis)
5 | // redis.on("error", function (err) {
6 | // console.log("Error " + err)
7 | // })
8 |
9 | // //存入redis
10 | // export function set(key, value, expire) {
11 | // return new Promise(function(resolve, reject) {
12 | // const multi = redis.multi()
13 | // multi.set(key, value)
14 | // if (expire) {
15 | // multi.expire(key, expire)
16 | // }
17 | // try {
18 | // multi.exec()
19 | // } catch (e) {
20 | // reject(e)
21 | // }
22 | // resolve()
23 | // })
24 | // }
25 |
26 | // //从redis中取出数据
27 | // export function get(key) {
28 | // return new Promise(function(resolve, reject) {
29 | // redis.get(key, (e, result) => {
30 | // if (e) {
31 | // reject(e)
32 | // }
33 | // else {
34 | // resolve(result)
35 | // }
36 | // })
37 | // })
38 | // }
39 |
--------------------------------------------------------------------------------
/wyfReader/app/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { AppRegistry, AsyncStorage } from 'react-native'
3 | import { persistStore, autoRehydrate } from 'redux-persist' // 状态持久化
4 | import axios from 'axios'
5 | import dva from './utils/dva'
6 | import Router from './router'
7 | import appModel from './models/app'
8 | import reader from './models/reader'
9 | import routerModel from './models/router'
10 |
11 | const app = dva({
12 | initialState: {},
13 | models: [appModel, routerModel, reader],
14 | extraEnhancers: [autoRehydrate()],
15 | onError(e) {
16 | console.log('onError', e)
17 | },
18 | })
19 | // 设置后台地址
20 | // axios.defaults.baseURL = 'http://localhost:5000'
21 | axios.defaults.baseURL = 'http://120.79.161.225/server'
22 | const App = app.start()
23 | persistStore(app.getStore(), {
24 | storage: AsyncStorage,
25 | blacklist: ['router'],
26 | })
27 |
28 | AppRegistry.registerComponent('wyfReader', () => App)
29 |
--------------------------------------------------------------------------------
/wu-server/src/models/chapters.js:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose'
2 |
3 | const Schema = mongoose.Schema
4 | const ObjectId = Schema.Types.ObjectId
5 |
6 | const Chapter = new mongoose.Schema({
7 | chapters: { type: Array },
8 | novel: { type: ObjectId, ref: 'novel'},
9 | })
10 |
11 | Chapter.statics = {
12 | getTitles: function (id){
13 | return this
14 | .find({novel: id})
15 | .exec()
16 | },
17 | getContent: function (id){
18 | return this
19 | .findById(id)
20 | .populate('novel',['name', 'url'])
21 | .exec()
22 | },
23 | findByNumber: function (id, num) {
24 | return this
25 | .findOne({number: num, novel: id})
26 | .exec()
27 | },
28 | getDirectory: function (options) {
29 | return this
30 | .find(options.where, options.attributes)
31 | .sort({number: options.order})
32 | .limit()
33 | .exec()
34 | }
35 | }
36 |
37 | export default mongoose.model('chapter', Chapter)
38 |
--------------------------------------------------------------------------------
/wyfReader/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | android.useDeprecatedNdk=true
21 |
--------------------------------------------------------------------------------
/wyfReader/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # Xcode
6 | #
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 | project.xcworkspace
24 |
25 | # Android/IntelliJ
26 | #
27 | build/
28 | .idea
29 | .gradle
30 | local.properties
31 | *.iml
32 |
33 | # node.js
34 | #
35 | node_modules/
36 | npm-debug.log
37 | yarn-error.log
38 |
39 | # BUCK
40 | buck-out/
41 | \.buckd/
42 | *.keystore
43 |
44 | # fastlane
45 | #
46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
47 | # screenshots whenever they are needed.
48 | # For more information about the recommended setup visit:
49 | # https://docs.fastlane.tools/best-practices/source-control/
50 |
51 | */fastlane/report.xml
52 | */fastlane/Preview.html
53 | */fastlane/screenshots
54 |
--------------------------------------------------------------------------------
/wu-server/src/modules/novels.js/router.js:
--------------------------------------------------------------------------------
1 | import { ensureUser } from '../../middleware/validators'
2 | import * as novel from './controller'
3 |
4 | export const baseUrl = '/novels'
5 |
6 | export default [
7 | {
8 | method: 'GET',
9 | route: '/:id',
10 | handlers: [
11 | novel.downloadChapters
12 | ]
13 | },
14 | {
15 | method: 'GET',
16 | route: '/search/zh',
17 | handlers: [
18 | novel.searchFromZH
19 | ]
20 | },
21 | {
22 | method: 'GET',
23 | route: '/search/bqk',
24 | handlers: [
25 | novel.searchFromBQK
26 | ]
27 | },
28 | {
29 | method: 'POST',
30 | route: '/search',
31 | handlers: [
32 | novel.searchNovel
33 | ]
34 | },
35 | {
36 | method: 'POST',
37 | route: '/acquire',
38 | handlers: [
39 | ensureUser,
40 | novel.getNovel
41 | ]
42 | },
43 | {
44 | method: 'GET',
45 | route: '/directory/:id',
46 | handlers: [
47 | novel.getDirectory
48 | ]
49 | }
50 | ]
51 |
--------------------------------------------------------------------------------
/wu-server/src/modules/index.js:
--------------------------------------------------------------------------------
1 | import glob from 'glob'
2 | import Router from 'koa-router'
3 |
4 | exports = module.exports = function initModules (app) {
5 | glob(`${__dirname}/*`, { ignore: '**/index.js' }, (err, matches) => {
6 | if (err) { throw err }
7 |
8 | matches.forEach((mod) => {
9 | const router = require(`${mod}/router`)
10 |
11 | const routes = router.default
12 | const baseUrl = router.baseUrl
13 | const instance = new Router({ prefix: baseUrl })
14 |
15 | routes.forEach((config) => {
16 | const {
17 | method = '',
18 | route = '',
19 | handlers = []
20 | } = config
21 |
22 | const lastHandler = handlers.pop()// 会修改原数组
23 | // 集成路由,支持多重回调
24 | instance[method.toLowerCase()](route, ...handlers, async function(ctx) {
25 | return await lastHandler(ctx)
26 | })
27 |
28 | app
29 | .use(instance.routes())
30 | .use(instance.allowedMethods())
31 | })
32 | })
33 | })
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 wuyafeiJS
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/wyfReader/app/models/router.js:
--------------------------------------------------------------------------------
1 | import { delay, NavigationActions } from '../utils'
2 | import { routerReducer } from '../router'
3 |
4 | const actions = [
5 | NavigationActions.BACK,
6 | NavigationActions.INIT,
7 | NavigationActions.NAVIGATE,
8 | NavigationActions.RESET,
9 | NavigationActions.SET_PARAMS,
10 | NavigationActions.URI,
11 | ]
12 |
13 | export default {
14 | namespace: 'router',
15 | state: {
16 | ...routerReducer(),
17 | },
18 | reducers: {
19 | apply(state, { payload: action }) {
20 | return routerReducer(state, action)
21 | },
22 | },
23 | effects: {
24 | watch: [
25 | function* watch({ take, call, put }) {
26 | const loop = true
27 | while (loop) {
28 | const payload = yield take(actions)
29 | yield put({
30 | type: 'apply',
31 | payload,
32 | })
33 | // debounce, see https://github.com/react-community/react-navigation/issues/271
34 | if (payload.type === 'Navigation/NAVIGATE') {
35 | yield call(delay, 500)
36 | }
37 | }
38 | },
39 | { type: 'watcher' },
40 | ],
41 | },
42 | }
43 |
--------------------------------------------------------------------------------
/wyfReader/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
19 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/wyfReader/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "env": {
4 | "browser": true
5 | },
6 | "extends": [
7 | "airbnb",
8 | "prettier",
9 | "prettier/react"
10 | ],
11 | "plugins": [
12 | "react",
13 | "jsx-a11y",
14 | "import",
15 | "prettier"
16 | ],
17 | "globals": {
18 | "__DEV__": true
19 | },
20 | "rules": {
21 | "arrow-parens": 0,
22 | "global-require": 0,
23 | "import/prefer-default-export": 0,
24 | "no-console": 0,
25 | "no-mixed-operators": 0,
26 | "no-use-before-define": 0,
27 | "radix": 0,
28 | "react/jsx-filename-extension": 0,
29 | "react/react-in-jsx-scope": 0,
30 | "react/prefer-stateless-function":0,
31 | "no-underscore-dangle":0,
32 | "react/prop-types": 0,
33 | "semi": [0, "never"],
34 | "spaced-comment": 0,
35 | "func-names": 0,
36 | "object-shorthand": 0,
37 | "object-shorthan": 0,
38 | "no-lonely-if": 0,
39 | "one-var": 0,
40 | "prefer-const": 0,
41 | "no-plusplus": 0,
42 | "camelcase": 0,
43 | "eqeqeq": 0,
44 | "react/no-string-refs": 0,
45 | "class-methods-use-this": 0,
46 | "react/self-closing-comp": 0,
47 | "react/jsx-no-bind": 0,
48 | "no-unused-vars": 1,
49 | "no-return-assign": 0,
50 | "react/sort-comp": 0
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/wu-server/config/passport.js:
--------------------------------------------------------------------------------
1 | import passport from 'koa-passport'
2 | import User from '../src/models/users'
3 | import { Strategy } from 'passport-local'
4 |
5 | /**
6 | * @param username 用户输入的用户名
7 | * @param password 用户输入的密码
8 | * @param done 验证完成后的回调函数
9 | */
10 | passport.use('local', new Strategy({
11 | usernameField: 'username',
12 | passwordField: 'password'
13 | }, async (username, password, done) => {
14 | try {
15 | const user = await User.findOne({ username })
16 | if (!user) { return done(null, false) }
17 |
18 | try {
19 | const isMatch = await user.validatePassword(password)
20 |
21 | if (!isMatch) { return done(null, false) }
22 |
23 | done(null, user)
24 | } catch (err) {
25 | done(err)
26 | }
27 | } catch (err) {
28 | return done(err)
29 | }
30 | }))
31 | // serializeUser 在用户登录验证成功后将会把用户的数据储存到 session 中
32 | passport.serializeUser((user, done) => {
33 | console.log('kkkkkkk')
34 | done(null, user.id)
35 | })
36 |
37 | // deserializeUser 在每次请求时候将从 session 中读用户对象
38 | passport.deserializeUser(async (id, done) => {
39 | console.log('deserializeUser, id: ' + id)
40 | try {
41 | const user = await User.findById(id, '-password')
42 | done(null, user)
43 | } catch (err) {
44 | done(err)
45 | }
46 | })
47 |
48 | export default passport
49 |
--------------------------------------------------------------------------------
/wyfReader/android/app/src/main/java/com/wyfreader/MainApplication.java:
--------------------------------------------------------------------------------
1 | package com.wyfreader;
2 |
3 | import android.app.Application;
4 |
5 | import com.facebook.react.ReactApplication;
6 | import com.horcrux.svg.SvgPackage;
7 | import com.facebook.react.ReactNativeHost;
8 | import com.facebook.react.ReactPackage;
9 | import com.facebook.react.shell.MainReactPackage;
10 | import com.facebook.soloader.SoLoader;
11 |
12 | import java.util.Arrays;
13 | import java.util.List;
14 |
15 | public class MainApplication extends Application implements ReactApplication {
16 |
17 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
18 | @Override
19 | public boolean getUseDeveloperSupport() {
20 | return BuildConfig.DEBUG;
21 | }
22 |
23 | @Override
24 | protected List getPackages() {
25 | return Arrays.asList(
26 | new MainReactPackage(),
27 | new SvgPackage()
28 | );
29 | }
30 |
31 | @Override
32 | protected String getJSMainModuleName() {
33 | return "index";
34 | }
35 | };
36 |
37 | @Override
38 | public ReactNativeHost getReactNativeHost() {
39 | return mReactNativeHost;
40 | }
41 |
42 | @Override
43 | public void onCreate() {
44 | super.onCreate();
45 | SoLoader.init(this, /* native exopackage */ false);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/wyfReader/App.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Sample React Native App
3 | * https://github.com/facebook/react-native
4 | * @flow
5 | */
6 |
7 | import React, { Component } from 'react';
8 | import {
9 | Platform,
10 | StyleSheet,
11 | Text,
12 | View
13 | } from 'react-native';
14 |
15 | const instructions = Platform.select({
16 | ios: 'Press Cmd+R to reload,\n' +
17 | 'Cmd+D or shake for dev menu',
18 | android: 'Double tap R on your keyboard to reload,\n' +
19 | 'Shake or press menu button for dev menu',
20 | });
21 |
22 | export default class App extends Component {
23 | render() {
24 | return (
25 |
26 |
27 | Welcome to React Native!wyf
28 |
29 |
30 | To get started, edit App.js
31 |
32 |
33 | {instructions}
34 |
35 |
36 | );
37 | }
38 | }
39 |
40 | const styles = StyleSheet.create({
41 | container: {
42 | flex: 1,
43 | justifyContent: 'center',
44 | alignItems: 'center',
45 | backgroundColor: '#F5FCFF',
46 | },
47 | welcome: {
48 | fontSize: 20,
49 | textAlign: 'center',
50 | margin: 10,
51 | },
52 | instructions: {
53 | textAlign: 'center',
54 | color: '#333333',
55 | marginBottom: 5,
56 | },
57 | });
58 |
--------------------------------------------------------------------------------
/wu-server/src/modules/auth/controller.js:
--------------------------------------------------------------------------------
1 | import passport from '../../../config/passport'
2 |
3 | /**
4 | * @apiDefine TokenError
5 | * @apiError Unauthorized Invalid JWT token
6 | *
7 | * @apiErrorExample {json} Unauthorized-Error:
8 | * HTTP/1.1 401 Unauthorized
9 | * {
10 | * "status": 401,
11 | * "error": "Unauthorized"
12 | * }
13 | */
14 | // 认证登录
15 | export async function authUser (ctx, next) {
16 | const code = ctx.request.body.code
17 | const captcha = ctx.session.captcha.toLowerCase()
18 | if (captcha !== code.toLowerCase()) {
19 | ctx.body = {
20 | code: -1,
21 | msg: '验证码错误'
22 | }
23 | return
24 | }
25 | return passport.authenticate('local', (user) => {
26 | if (!user) {
27 | // ctx.throw(401)
28 | ctx.body = {
29 | code: 11,
30 | msg: '账号或密码错误'
31 | }
32 | return
33 | }
34 |
35 | const token = user.generateToken()
36 |
37 | const response = user.toJSON()
38 | delete response.password
39 | ctx.body = {
40 | code: 0,
41 | token,
42 | username: response.username
43 | }
44 | ctx.login(user)
45 | })(ctx, next)
46 | }
47 | // 测试是否认证
48 | export async function testUser (ctx, next) {
49 | console.log(ctx.isAuthenticated(), '=======')
50 | if (ctx.isAuthenticated()) {
51 | ctx.body = '认证通过'
52 | } else {
53 | ctx.throw(401)
54 | ctx.body = '非法访问'
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/wu-server/src/utils/handle/index.js:
--------------------------------------------------------------------------------
1 | //全局函数
2 |
3 | import nodemailer from 'nodemailer'
4 | import smtpTransport from 'nodemailer-smtp-transport'
5 | import wellknown from 'nodemailer-wellknown'
6 | import app from '../../../bin/server'
7 | // import * as Redis from '../redis'
8 |
9 | const handle = {}
10 |
11 | //统计api访问量
12 | handle.count = async (key) => {
13 | try {
14 | return true
15 | // let num = await Redis.get(key)
16 | // let newNum = num === 'NaN' ? 0 : parseInt(num) + 1
17 | // await Redis.set(key, newNum)
18 | } catch (e) {
19 | Handle.sendEmail(e.message)
20 | }
21 | }
22 |
23 | handle.success = (data = {}) => {
24 | return {
25 | success: true,
26 | code: 0,
27 | data: data
28 | }
29 | }
30 |
31 | //报错时,发送邮件
32 | handle.sendEmail = (mes) => {
33 |
34 | const config = wellknown("QQ")
35 | config.auth = {
36 | user: '',
37 | pass: '',
38 | }
39 |
40 | const transporter = nodemailer.createTransport(smtpTransport(config))
41 |
42 | const mailOptions = {
43 | from: '',
44 | to: '',
45 | subject: '妈的又出Bug了,赶紧去调。',
46 | text: '',
47 | html: mes,
48 | }
49 |
50 | if (app.env === 'production') {
51 | transporter.sendMail(mailOptions, function(error, info){
52 | if(error){
53 | console.log(error)
54 | }
55 | else {
56 | console.log('Message sent: ' + info.response)
57 | }
58 |
59 | })
60 | }
61 |
62 | }
63 |
64 |
65 |
66 |
67 | module.exports = handle
68 |
--------------------------------------------------------------------------------
/wyfReader/ios/wyfReader/AppDelegate.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | */
9 |
10 | #import "AppDelegate.h"
11 |
12 | #import
13 | #import
14 |
15 | @implementation AppDelegate
16 |
17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
18 | {
19 | NSURL *jsCodeLocation;
20 |
21 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
22 |
23 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
24 | moduleName:@"wyfReader"
25 | initialProperties:nil
26 | launchOptions:launchOptions];
27 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
28 |
29 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
30 | UIViewController *rootViewController = [UIViewController new];
31 | rootViewController.view = rootView;
32 | self.window.rootViewController = rootViewController;
33 | [self.window makeKeyAndVisible];
34 | return YES;
35 | }
36 |
37 | @end
38 |
--------------------------------------------------------------------------------
/wyfReader/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wyf-reader",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "start": "node node_modules/react-native/local-cli/cli.js start",
7 | "test": "jest",
8 | "prettier": "prettier --write --single-quote --no-semi --trailing-comma es5 --print-width 80 \"app/**/*.js\"",
9 | "lint": "eslint app",
10 | "format": "yarn prettier && yarn lint -- --fix",
11 | "precommit": "yarn format"
12 | },
13 | "dependencies": {
14 | "antd-mobile": "^2.1.1",
15 | "axios": "^0.17.1",
16 | "dva-core": "^0.1.3",
17 | "dva-loading": "^1.0.4",
18 | "rc-form": "^2.1.7",
19 | "react": "16.0.0",
20 | "react-native": "0.50.4",
21 | "react-native-device-info": "^0.13.0",
22 | "react-native-svg": "^6.0.1-rc.3",
23 | "react-native-swipeout": "^2.3.3",
24 | "react-navigation": "^1.0.0-beta.11",
25 | "react-redux": "^5.0.6",
26 | "redux-persist": "^4.8.3"
27 | },
28 | "devDependencies": {
29 | "babel-eslint": "^7.2.3",
30 | "babel-jest": "21.2.0",
31 | "babel-plugin-import": "^1.6.3",
32 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
33 | "babel-preset-react-native": "4.0.0",
34 | "eslint": "^4.4.1",
35 | "eslint-config-airbnb": "^15.1.0",
36 | "eslint-config-prettier": "^2.3.0",
37 | "eslint-plugin-babel": "^4.1.2",
38 | "eslint-plugin-import": "^2.7.0",
39 | "eslint-plugin-jsx-a11y": "^5.1.1",
40 | "eslint-plugin-prettier": "^2.1.2",
41 | "eslint-plugin-react": "^7.2.0",
42 | "husky": "^0.14.3",
43 | "jest": "21.2.1",
44 | "prettier": "^1.5.3",
45 | "react-test-renderer": "16.0.0"
46 | },
47 | "jest": {
48 | "preset": "react-native"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/wyfReader/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | ; We fork some components by platform
3 | .*/*[.]android.js
4 |
5 | ; Ignore "BUCK" generated dirs
6 | /\.buckd/
7 |
8 | ; Ignore unexpected extra "@providesModule"
9 | .*/node_modules/.*/node_modules/fbjs/.*
10 |
11 | ; Ignore duplicate module providers
12 | ; For RN Apps installed via npm, "Libraries" folder is inside
13 | ; "node_modules/react-native" but in the source repo it is in the root
14 | .*/Libraries/react-native/React.js
15 |
16 | ; Ignore polyfills
17 | .*/Libraries/polyfills/.*
18 |
19 | [include]
20 |
21 | [libs]
22 | node_modules/react-native/Libraries/react-native/react-native-interface.js
23 | node_modules/react-native/flow/
24 |
25 | [options]
26 | emoji=true
27 |
28 | module.system=haste
29 |
30 | munge_underscores=true
31 |
32 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
33 |
34 | suppress_type=$FlowIssue
35 | suppress_type=$FlowFixMe
36 | suppress_type=$FlowFixMeProps
37 | suppress_type=$FlowFixMeState
38 | suppress_type=$FixMe
39 |
40 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
41 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-6]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
42 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
43 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
44 |
45 | unsafe.enable_getters_and_setters=true
46 |
47 | [version]
48 | ^0.56.0
49 |
--------------------------------------------------------------------------------
/wyfReader/app/utils/request.js:
--------------------------------------------------------------------------------
1 | // import axios from 'axios'
2 | // import { Toast } from 'antd-mobile'
3 | // // let checkStatus=(response)=>{
4 | // // if (response.status >= 200 && response.status < 300) {
5 | // // return response;
6 | // // }
7 | // // const error = new Error(response.statusText);
8 | // // error.response = response;
9 | // // throw error;
10 | // // }
11 |
12 | // const handledata=(data)=>{
13 | // if(!data) data={ msg: '研发GG正在加班修复中,请您稍后再试' }
14 | // if(data.code === 0) {
15 | // Toast.hide()
16 | // // if(data.msg) Toast.success('操作成功',4);
17 | // return data
18 | // }
19 | // // else if(data.code == 401 || data.code == 11){
20 | // // if(data.msg) Toast.fail('未绑定广发通账号', 2);
21 | // // }
22 |
23 | // Toast.hide()
24 | // return data
25 |
26 | // }
27 |
28 | // const handleError=(err)=>{
29 | // Toast.hide()
30 | // Toast.fail(err.msg||'火星网络传输慢,下拉返回地球哦~',2)
31 | // }
32 |
33 | // /**
34 | // * @param {string} url The URL we want to request
35 | // * @param {object} [options] The options we want to pass to "fetch"
36 | // * @return {object} An object containing either "data" or "err"
37 | // */
38 |
39 | // export default async function request(url, options) {
40 | // const path = `http://localhost:5000${url}`
41 | // // Toast.loading('Loading...',10);
42 | // const response = await fetch(path, {...options,credentials:'include'})
43 | // try {
44 | // const data = await response.json()
45 | // handledata(data)
46 | // const ret = {
47 | // data
48 | // }
49 | // return ret
50 | // } catch (err) {
51 | // handleError(err)
52 | // }
53 | // }
--------------------------------------------------------------------------------
/wyfReader/app/models/reader.js:
--------------------------------------------------------------------------------
1 | import { Toast } from 'antd-mobile'
2 | import { createAction, NavigationActions } from '../utils'
3 | import * as authService from '../services/auth'
4 | import * as services from '../services/app'
5 |
6 | export default {
7 | namespace: 'reader',
8 | state: {
9 | },
10 | reducers: {
11 | receiveRenderChapters(state, { payload: { id } }) {
12 | return { ...state, id }
13 | },
14 | receiveFirstChapters(state, { payload: { firstChapter } }) {
15 | return { ...state, firstChapter }
16 | },
17 | receiveChapters(state, { payload: { chapter } }) {
18 | return { ...state, chapter }
19 | },
20 | receiveDirectory(state, { payload: { directory } }) {
21 | return { ...state, directory }
22 | },
23 | },
24 | effects: {
25 | *getFirstRenderChapters({ payload: { id, num } }, { call, put }) {
26 | const { data } = yield call(services.getFirstRenderChapters, id, num)
27 | yield put({
28 | type: 'receiveFirstChapters',
29 | payload: {
30 | firstChapter: data.data.response
31 | }
32 | })
33 | },
34 | *getChapters({ payload: { id, num } }, { call, put }) {
35 | const { data } = yield call(services.getChapters, id, num)
36 | yield put({
37 | type: 'receiveChapters',
38 | payload: {
39 | chapter: data.data.response
40 | }
41 | })
42 | },
43 | *getDirectory({ payload: { id, order } }, { call, put }) {
44 | const { data } = yield call(services.getDirectory, id, order)
45 | yield put({
46 | type: 'receiveDirectory',
47 | payload: {
48 | directory: data.data.chapters
49 | }
50 | })
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/wyfReader/ios/wyfReader/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleIconFile
6 |
7 | NSAppTransportSecurity
8 |
9 | NSAllowsArbitraryLoads
10 |
11 |
12 | LSApplicationCategoryType
13 |
14 | CFBundleGetInfoString
15 |
16 | CFBundleDevelopmentRegion
17 | en
18 | CFBundleDisplayName
19 | wyfReader
20 | CFBundleExecutable
21 | $(EXECUTABLE_NAME)
22 | CFBundleIdentifier
23 | $(PRODUCT_BUNDLE_IDENTIFIER)
24 | CFBundleInfoDictionaryVersion
25 | 6.0
26 | CFBundleName
27 | $(PRODUCT_NAME)
28 | CFBundlePackageType
29 | APPL
30 | CFBundleShortVersionString
31 | 1.0
32 | CFBundleSignature
33 | ????
34 | CFBundleVersion
35 | 1
36 | LSRequiresIPhoneOS
37 |
38 | NSLocationWhenInUseUsageDescription
39 |
40 | UILaunchStoryboardName
41 | LaunchScreen
42 | UIRequiredDeviceCapabilities
43 |
44 | armv7
45 |
46 | UISupportedInterfaceOrientations
47 |
48 | UIInterfaceOrientationPortrait
49 | UIInterfaceOrientationLandscapeLeft
50 | UIInterfaceOrientationLandscapeRight
51 |
52 | UIViewControllerBasedStatusBarAppearance
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/wu-server/bin/server.js:
--------------------------------------------------------------------------------
1 | import Koa from 'koa'
2 | import bodyParser from 'koa-bodyparser'
3 | import convert from 'koa-convert'
4 | import logger from 'koa-logger'
5 | import mongoose from 'mongoose'
6 | import session from 'koa-generic-session'
7 | import passport from '../config/passport'
8 | import mount from 'koa-mount'
9 | import serve from 'koa-static'
10 |
11 | import config from '../config'
12 | import handle from '../src/utils/handle'
13 | import { errorMiddleware } from '../src/middleware'
14 |
15 | // 性能监控
16 | // const easyMonitor = require('easy-monitor')
17 | // easyMonitor({
18 | // cluster: true,
19 | // bootstrap: 'embrace',
20 | // project_name: 'wu-server',
21 | // /**
22 | // @param {string} tcp_host 填写你部署的 dashboard 进程所在的服务器 ip
23 | // @param {number} tcp_port 填写你部署的 dashboard 进程所在的服务器 端口
24 | // **/
25 | // embrace: {
26 | // tcp_host: '127.0.0.1',
27 | // tcp_port: 26666
28 | // }
29 | // })
30 |
31 | global.Handle = handle
32 |
33 | const app = new Koa()
34 | app.keys = [config.session]
35 |
36 | mongoose.Promise = global.Promise
37 | mongoose.connect(config.database)
38 |
39 | app.use(convert(logger()))
40 | app.use(bodyParser())
41 | app.use(convert(session()))
42 | app.use(errorMiddleware())
43 |
44 | app.use(convert(mount('/docs', serve(`${process.cwd()}/docs`))))
45 |
46 | // require('../config/passport')
47 | app.use(passport.initialize())
48 | app.use(passport.session())
49 |
50 | const modules = require('../src/modules')
51 | modules(app)
52 |
53 | app.listen(config.port, () => {
54 | console.log(`Server started on ${config.port}`)
55 | })
56 |
57 | // 每天凌晨准时更新小说爬虫
58 | // const UpdateNovel = require('../src/utils/updateNovel')
59 | // if (app.env === 'production') {
60 | // UpdateNovel.update()
61 | // }
62 | require('../src/utils/schedule/updateChapter')
63 |
64 | export default app
65 |
--------------------------------------------------------------------------------
/wyfReader/ios/wyfReader-tvOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UIViewControllerBasedStatusBarAppearance
38 |
39 | NSLocationWhenInUseUsageDescription
40 |
41 | NSAppTransportSecurity
42 |
43 |
44 | NSExceptionDomains
45 |
46 | localhost
47 |
48 | NSExceptionAllowsInsecureHTTPLoads
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/wyfReader/android/app/BUCK:
--------------------------------------------------------------------------------
1 | # To learn about Buck see [Docs](https://buckbuild.com/).
2 | # To run your application with Buck:
3 | # - install Buck
4 | # - `npm start` - to start the packager
5 | # - `cd android`
6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
8 | # - `buck install -r android/app` - compile, install and run application
9 | #
10 |
11 | lib_deps = []
12 |
13 | for jarfile in glob(['libs/*.jar']):
14 | name = 'jars__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')]
15 | lib_deps.append(':' + name)
16 | prebuilt_jar(
17 | name = name,
18 | binary_jar = jarfile,
19 | )
20 |
21 | for aarfile in glob(['libs/*.aar']):
22 | name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')]
23 | lib_deps.append(':' + name)
24 | android_prebuilt_aar(
25 | name = name,
26 | aar = aarfile,
27 | )
28 |
29 | android_library(
30 | name = "all-libs",
31 | exported_deps = lib_deps,
32 | )
33 |
34 | android_library(
35 | name = "app-code",
36 | srcs = glob([
37 | "src/main/java/**/*.java",
38 | ]),
39 | deps = [
40 | ":all-libs",
41 | ":build_config",
42 | ":res",
43 | ],
44 | )
45 |
46 | android_build_config(
47 | name = "build_config",
48 | package = "com.wyfreader",
49 | )
50 |
51 | android_resource(
52 | name = "res",
53 | package = "com.wyfreader",
54 | res = "src/main/res",
55 | )
56 |
57 | android_binary(
58 | name = "app",
59 | keystore = "//android/keystores:debug",
60 | manifest = "src/main/AndroidManifest.xml",
61 | package_type = "debug",
62 | deps = [
63 | ":app-code",
64 | ],
65 | )
66 |
--------------------------------------------------------------------------------
/wu-server/src/models/users.js:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose'
2 | import bcrypt from 'bcrypt'
3 | import config from '../../config'
4 | import jwt from 'jsonwebtoken'
5 |
6 | const User = new mongoose.Schema({
7 | type: { type: String, default: 'User' },
8 | name: { type: String },
9 | email: { type: String },
10 | username: { type: String },
11 | password: { type: String },
12 | uuid: { type: String },
13 | meta: {
14 | createAt: {
15 | type: Date,
16 | default: Date.now()
17 | },
18 | update: {
19 | type: Date,
20 | default: Date.now()
21 | }
22 | }
23 | })
24 |
25 | User.pre('save', function preSave (next) {
26 | const user = this
27 |
28 | if (this.isNew) {
29 | this.meta.createAt = this.meta.update = Date.now()
30 | } else {
31 | this.meta.update = Date.now
32 | }
33 | if (this.uuid && !this.username) {
34 | return next()
35 | }
36 | new Promise((resolve, reject) => {
37 | bcrypt.genSalt(10, (err, salt) => {
38 | if (err) {
39 | return reject(err)
40 | }
41 | resolve(salt)
42 | })
43 | })
44 | .then(salt => {
45 | bcrypt.hash(user.password, salt, (err, hash) => {
46 | if (err) {
47 | throw new Error(err)
48 | }
49 | user.password = hash
50 | return next()
51 | })
52 | })
53 | .catch(err => {
54 | next(err)
55 | })
56 | })
57 |
58 | User.methods.validatePassword = function validatePassword(password) {
59 | const user = this
60 | return new Promise((resolve, reject) => {
61 | bcrypt.compare(password, user.password, (err, isMatch) => {
62 | if (err) {
63 | return reject(err)
64 | }
65 |
66 | resolve(isMatch)
67 | })
68 | })
69 | }
70 |
71 | User.methods.generateToken = function generateToken() {
72 | const user = this
73 |
74 | return jwt.sign({ id: user.id }, config.token)
75 | };
76 |
77 | export default mongoose.model('user', User)
78 |
--------------------------------------------------------------------------------
/wyfReader/app/containers/Account.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { StyleSheet, View, Image, Text, Button, AsyncStorage } from 'react-native'
3 | import { connect } from 'react-redux'
4 | import { NavigationActions } from '../utils'
5 |
6 | @connect()
7 | class Account extends Component {
8 | static navigationOptions = {
9 | title: 'Account',
10 | tabBarLabel: 'Account',
11 | tabBarIcon: ({ focused, tintColor }) =>
12 | ,
16 | }
17 | constructor(props) {
18 | super(props)
19 | this.state = {
20 | username: null
21 | }
22 | }
23 | componentWillMount() {
24 | let that = this
25 | AsyncStorage.getItem('username').then((username) => {
26 | that.setState({
27 | username
28 | })
29 | })
30 | }
31 | gotoLogin = () => {
32 | this.props.dispatch(NavigationActions.navigate({ routeName: 'Login' }))
33 | }
34 | loginout = () => {
35 | AsyncStorage.removeItem('username');
36 | AsyncStorage.removeItem('userToken');
37 | this.props.dispatch({type: 'app/receiveToken', payload:{token: ''}})
38 | this.props.dispatch({type: 'app/getBookList', payload:{}})
39 | this.setState({
40 | username: null
41 | })
42 | }
43 | render() {
44 | console.log(this.state.username,888)
45 | return (
46 |
47 | {this.state.username ? 欢迎你,{this.state.username} :}
48 |
49 | )
50 | }
51 | }
52 |
53 | const styles = StyleSheet.create({
54 | container: {
55 | flex: 1,
56 | alignItems: 'center',
57 | justifyContent: 'center',
58 | },
59 | icon: {
60 | width: 32,
61 | height: 32,
62 | },
63 | })
64 |
65 | export default Account
66 |
--------------------------------------------------------------------------------
/wyfReader/ios/wyfReaderTests/wyfReaderTests.m:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | */
9 |
10 | #import
11 | #import
12 |
13 | #import
14 | #import
15 |
16 | #define TIMEOUT_SECONDS 600
17 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!"
18 |
19 | @interface wyfReaderTests : XCTestCase
20 |
21 | @end
22 |
23 | @implementation wyfReaderTests
24 |
25 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
26 | {
27 | if (test(view)) {
28 | return YES;
29 | }
30 | for (UIView *subview in [view subviews]) {
31 | if ([self findSubviewInView:subview matching:test]) {
32 | return YES;
33 | }
34 | }
35 | return NO;
36 | }
37 |
38 | - (void)testRendersWelcomeScreen
39 | {
40 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
41 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
42 | BOOL foundElement = NO;
43 |
44 | __block NSString *redboxError = nil;
45 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
46 | if (level >= RCTLogLevelError) {
47 | redboxError = message;
48 | }
49 | });
50 |
51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
54 |
55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
57 | return YES;
58 | }
59 | return NO;
60 | }];
61 | }
62 |
63 | RCTSetLogFunction(RCTDefaultLogFunction);
64 |
65 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
66 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
67 | }
68 |
69 |
70 | @end
71 |
--------------------------------------------------------------------------------
/wu-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wu_server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "nodemon index.js",
8 | "serve:prod": "node NODE_ENV=production index.js",
9 | "dev": "./node_modules/.bin/nodemon index.js",
10 | "test": "NODE_ENV=test ./node_modules/.bin/mocha --compilers js:babel-register --require babel-polyfill",
11 | "lint": "eslint src/**/*.js",
12 | "docs": "./node_modules/.bin/apidoc -i src/ -o docs"
13 | },
14 | "keywords": [
15 | "api",
16 | "koa",
17 | "koa2",
18 | "boilerplate",
19 | "es6",
20 | "mongoose",
21 | "passportjs",
22 | "apidoc"
23 | ],
24 | "author": "XiaoWu",
25 | "license": "MIT",
26 | "apidoc": {
27 | "title": "YCool--API文档",
28 | "url": "localhost:5000"
29 | },
30 | "repository": {
31 | "type": "git",
32 | "url": "https://github.com/dlyt/YCool_Server.git"
33 | },
34 | "dependencies": {
35 | "apidoc": "^0.17.5",
36 | "babel-core": "^6.5.1",
37 | "babel-polyfill": "^6.5.0",
38 | "babel-preset-es2015-node5": "^1.2.0",
39 | "babel-preset-stage-0": "^6.5.0",
40 | "bcrypt": "^1.0.2",
41 | "cheerio": "^0.22.0",
42 | "easy-monitor": "^2.2.1",
43 | "glob": "^7.0.0",
44 | "iconv-lite": "^0.4.15",
45 | "joi": "^13.1.1",
46 | "jsonwebtoken": "^7.1.9",
47 | "koa": "^2.0.0-alpha.6",
48 | "koa-bodyparser": "^3.0.0",
49 | "koa-convert": "^1.2.0",
50 | "koa-generic-session": "^1.10.1",
51 | "koa-logger": "^2.0.0",
52 | "koa-mount": "^1.3.0",
53 | "koa-passport": "^2.0.1",
54 | "koa-router": "^7.0.1",
55 | "koa-session2": "^2.2.5",
56 | "koa-static": "^2.0.0",
57 | "mongoose": "^4.4.3",
58 | "node-schedule": "^1.2.1",
59 | "nodemailer": "^3.1.7",
60 | "nodemailer-smtp-transport": "^2.7.2",
61 | "nodemailer-wellknown": "^0.2.2",
62 | "passport-local": "^1.0.0",
63 | "pinyin": "^2.8.0",
64 | "redis": "^2.7.1",
65 | "request": "^2.79.0",
66 | "simple-pinyin": "^3.0.2",
67 | "svg-captcha": "^1.3.11"
68 | },
69 | "devDependencies": {
70 | "babel-eslint": "^6.0.2",
71 | "babel-register": "^6.5.1",
72 | "chai": "^3.5.0",
73 | "eslint": "^3.4.0",
74 | "eslint-config-standard": "^6.0.0",
75 | "eslint-plugin-promise": "^2.0.1",
76 | "eslint-plugin-standard": "^2.0.0",
77 | "mocha": "^3.0.2",
78 | "nodemon": "^1.8.1",
79 | "supertest": "^2.0.0"
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/wyfReader/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | ### Node ###
3 | # Logs
4 | wu-server/logs
5 | *.log
6 | wu-server/npm-debug.log*
7 |
8 | # Runtime data
9 | wu-server/pids
10 | wu-server/*.pid
11 | wu-server/*.seed
12 |
13 | # Directory for instrumented libs generated by jscoverage/JSCover
14 | wu-server/lib-cov
15 |
16 | # Coverage directory used by tools like istanbul
17 | wu-server/coverage
18 |
19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
20 | wu-server/.grunt
21 |
22 | # node-waf configuration
23 | wu-server/.lock-wscript
24 |
25 | # Compiled binary addons (http://nodejs.org/api/addons.html)
26 | wu-server/build/Release
27 |
28 | # Dependency directory
29 | wu-server/node_modules
30 |
31 | # Optional npm cache directory
32 | wu-server/.npm
33 |
34 | # Optional REPL history
35 | wu-server/.node_repl_history
36 |
37 |
38 | ### SublimeText ###
39 | # cache files for sublime text
40 | wu-server/*.tmlanguage.cache
41 | wu-server/*.tmPreferences.cache
42 | wu-server/*.stTheme.cache
43 |
44 | # workspace files are user-specific
45 | wu-server/*.sublime-workspace
46 |
47 | # project files should be checked into the repository, unless a significant
48 | # proportion of contributors will probably not be using SublimeText
49 | wu-server/*.sublime-project
50 |
51 | # sftp configuration file
52 | wu-server/sftp-config.json
53 |
54 | #Documentation
55 | wu-server/docs
56 |
57 |
58 | # OSX wyfReader
59 | #
60 | .DS_Store
61 |
62 | # Xcode
63 | #
64 | wyfReader/build/
65 | wyfReader/*.pbxuser
66 | !wyfReader/default.pbxuser
67 | wyfReader/*.mode1v3
68 | !wyfReader/default.mode1v3
69 | wyfReader/*.mode2v3
70 | !wyfReader/default.mode2v3
71 | wyfReader/*.perspectivev3
72 | !wyfReader/default.perspectivev3
73 | wyfReader/xcuserdata
74 | wyfReader/*.xccheckout
75 | wyfReader/*.moved-aside
76 | wyfReader/DerivedData
77 | wyfReader/*.hmap
78 | wyfReader/*.ipa
79 | wyfReader/*.xcuserstate
80 | wyfReader/project.xcworkspace
81 |
82 | # Android/IntelliJ
83 | #
84 | wyfReader/build/
85 | wyfReader/.idea
86 | wyfReader/.gradle
87 | wyfReader/local.properties
88 | wyfReader/*.iml
89 |
90 | # node.js
91 | #
92 | wyfReader/node_modules/
93 | wyfReader/npm-debug.log
94 | wyfReader/yarn-error.log
95 |
96 | # BUCK
97 | wyfReader/buck-out/
98 | wyfReader/\.buckd/
99 | wyfReader/*.keystore
100 |
101 | # fastlane
102 | #
103 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
104 | # screenshots whenever they are needed.
105 | # For more information about the recommended setup visit:
106 | # https://docs.fastlane.tools/best-practices/source-control/
107 |
108 | wyfReader/*/fastlane/report.xml
109 | wyfReader/*/fastlane/Preview.html
110 | wyfReader/*/fastlane/screenshots
111 |
--------------------------------------------------------------------------------
/wyfReader/android/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Disabling obfuscation is useful if you collect stack traces from production crashes
20 | # (unless you are using a system that supports de-obfuscate the stack traces).
21 | -dontobfuscate
22 |
23 | # React Native
24 |
25 | # Keep our interfaces so they can be used by other ProGuard rules.
26 | # See http://sourceforge.net/p/proguard/bugs/466/
27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
29 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip
30 |
31 | # Do not strip any method/class that is annotated with @DoNotStrip
32 | -keep @com.facebook.proguard.annotations.DoNotStrip class *
33 | -keep @com.facebook.common.internal.DoNotStrip class *
34 | -keepclassmembers class * {
35 | @com.facebook.proguard.annotations.DoNotStrip *;
36 | @com.facebook.common.internal.DoNotStrip *;
37 | }
38 |
39 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
40 | void set*(***);
41 | *** get*();
42 | }
43 |
44 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
45 | -keep class * extends com.facebook.react.bridge.NativeModule { *; }
46 | -keepclassmembers,includedescriptorclasses class * { native ; }
47 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; }
48 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; }
49 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; }
50 |
51 | -dontwarn com.facebook.react.**
52 |
53 | # TextLayoutBuilder uses a non-public Android constructor within StaticLayout.
54 | # See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details.
55 | -dontwarn android.text.StaticLayout
56 |
57 | # okhttp
58 |
59 | -keepattributes Signature
60 | -keepattributes *Annotation*
61 | -keep class okhttp3.** { *; }
62 | -keep interface okhttp3.** { *; }
63 | -dontwarn okhttp3.**
64 |
65 | # okio
66 |
67 | -keep class sun.misc.Unsafe { *; }
68 | -dontwarn java.nio.file.*
69 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
70 | -dontwarn okio.**
71 |
--------------------------------------------------------------------------------
/wyfReader/app/services/app.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { AsyncStorage } from 'react-native'
3 | import DeviceInfo from "react-native-device-info";
4 | import setAuthorizationToken from "../utils/setAuthorizationToken";
5 |
6 | const handleData = data => {
7 | let msg;
8 | if (!data) msg = { msg: "研发GG正在加班修复中,请您稍后再试" };
9 | if (data.success === true) {
10 | msg = "操作成功";
11 | } else {
12 | msg = "操作失败";
13 | }
14 | alert(msg);
15 | return { data };
16 | };
17 | export function getAuth() {
18 | const uuid = DeviceInfo.getUniqueID();
19 | const json = { user: { uuid } };
20 | return axios.post("/users/tourists", json).then(res => {
21 | setAuthorizationToken(res.data.token);
22 | AsyncStorage.setItem(`userToken`, res.data.token)
23 | return { data: res };
24 | });
25 | }
26 |
27 | export function register(loginData) {
28 | return axios.post("/users/register", loginData).then(res => ({ data: res }), err => (console.log(err)));
29 | }
30 |
31 | export function login(loginData) {
32 | return axios.post("/auth", loginData).then(res => {
33 | console.log(res,99)
34 | if (res.data.code === 0) {
35 | setAuthorizationToken(res.data.token);
36 | console.log(res.data)
37 | AsyncStorage.multiSet([['username', res.data.username], ['userToken', res.data.token]])
38 | }
39 | return { data: res };
40 | }, err => {
41 | alert('登录失败,账号或密码错误')
42 | });
43 | }
44 | export function getBookshelfTourist(token) {
45 | setAuthorizationToken(token);
46 | return axios
47 | .get("/bookshelfs")
48 | .then(res => ({ data: res }), err => console.error(err));
49 | }
50 | export function searchBookWords(text) {
51 | return axios
52 | .get(`/novels/search/zh?keyword=${text}`)
53 | .then(res => ({ data: res }), err => console.error(err));
54 | }
55 | export function getNovelList(name) {
56 | return axios
57 | .get(`/novels/search/bqk?name=${name}`)
58 | .then(res => ({ data: res }), err => console.error(err));
59 | }
60 | export function getNovelInfo(name, url) {
61 | const json = {
62 | novel: {
63 | name: name,
64 | url: url
65 | }
66 | };
67 | return axios
68 | .post("/novels/acquire", json)
69 | .then(res => ({ data: res }), err => console.error(err));
70 | }
71 | export function orderNovel(id) {
72 | return axios.post('/bookshelfs/order', {id: id})
73 | .then(res => { console.log(res) })
74 | }
75 |
76 | export function deleteNovel(id) {
77 | return axios.post('/bookshelfs/delete', {id: id})
78 | .then(res => { console.log(res) })
79 | }
80 |
81 | export function getFirstRenderChapters(id, num) {
82 | return axios
83 | .get(`/chapters/firstRender?id=${id}&num=${num}`)
84 | .then(res => ({ data: res }), err => console.error(err));
85 | }
86 |
87 | export function getChapters(id, num) {
88 | let json = {
89 | id,
90 | num
91 | }
92 | return axios
93 | .post(`/chapters`, json)
94 | .then(res => ({ data: res }), err => console.error(err));
95 | }
96 |
97 | export function getDirectory(id, order) {
98 | return axios
99 | .get(`/novels/directory/${id}?order=${order}`)
100 | .then(res => ({ data: res }), err => console.error(err));
101 | }
--------------------------------------------------------------------------------
/wu-server/src/utils/updateNovel.js:
--------------------------------------------------------------------------------
1 | import Bookshelf from '../models/bookshelfs'
2 | import Novel from '../models/novels'
3 | import * as Crawler from './crawler'
4 |
5 | import nodemailer from 'nodemailer'
6 | import wellknown from 'nodemailer-wellknown'
7 | import smtpTransport from 'nodemailer-smtp-transport'
8 |
9 | export async function update () {
10 | let crawlerList, // 获取所有需要更新提醒的小说
11 | $, // DOM
12 | length, // 章节数量
13 | chapterArr, // 所有a标签里包含的数组
14 | count
15 | try {
16 | // 将需要爬取的小说类型设置成VIP
17 | crawlerList = await Novel.find({ type: 'VIP' })
18 | } catch (e) {
19 | Handle.sendEmail(e.message)
20 | }
21 |
22 | if (crawlerList) {
23 | for (let item of crawlerList.values()) {
24 | try {
25 | $ = await Crawler.getHtml(item.url)
26 | } catch (e) {
27 | Handle.sendEmail(e.message)
28 | }
29 |
30 | chapterArr = $('#list dd a')
31 | length = $('#list dd').length
32 | count = parseInt(item.countChapter)
33 | console.log(length, count, '===========')
34 | // 如果爬取的章节列表数量不等于数据库中的数量,爬取余下章节
35 | if (count !== length) {
36 | // for (let i = count; i < length; i++) {
37 | // chapter = new Chapter({
38 | // postfix: chapterArr[i].attribs.href,
39 | // title: chapterArr[i].children[0].data,
40 | // number: i + 1,
41 | // novel: item.id
42 | // })
43 | // try {
44 | // await chapter.save()
45 | // } catch (e) {
46 | // Handle.sendEmail(e.message)
47 | // }
48 | // }
49 | try {
50 | await Crawler.getNovel($, item._id)
51 | } catch (e) {
52 | Handle.sendEmail(e.message)
53 | }
54 |
55 | item.updateTime = $('#info p')[2].children[0].data.substring(
56 | 5,
57 | $('#info p')[2].children[0].data.length
58 | )
59 | item.countChapter = length
60 | item.lastChapterTitle = chapterArr[length - 1].children[0].data
61 | try {
62 | await item.save()
63 | } catch (e) {
64 | Handle.sendEmail(e.message)
65 | }
66 | // 发送邮件并更新小说信息
67 | sendRemindEmail(item)
68 | }
69 | }
70 | }
71 | }
72 |
73 | async function sendRemindEmail (novel) {
74 | let userEmails
75 | try {
76 | userEmails = await Bookshelf.find({ novel: novel.id })
77 | .populate('user', ['email'])
78 | .populate('novel', ['name'])
79 | .exec()
80 | } catch (e) {
81 | Handle.sendEmail(e.message)
82 | }
83 |
84 | userEmails.forEach(function (item) {
85 | const email = item.user.email
86 | const name = item.novel.name
87 | const config = wellknown('QQ')
88 | config.auth = {
89 | user: '',
90 | pass: ''
91 | }
92 |
93 | const transporter = nodemailer.createTransport(smtpTransport(config))
94 |
95 | const mailOptions = {
96 | from: '',
97 | to: email,
98 | subject: `${name}更新了,Happy!`,
99 | text: '',
100 | html: ''
101 | }
102 |
103 | transporter.sendMail(mailOptions, function (error, info) {
104 | if (error) {
105 | console.log(error)
106 | Handle.sendEmail(error.message)
107 | } else {
108 | console.log('Message sent: ' + info.response)
109 | }
110 | })
111 | })
112 | }
113 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## RN+dva+node+mongo+nginx+docker 从开发到部署,全栈入坑指引!
2 |
3 | ### 前言
4 |
5 | 作为一个优秀前端er,除了要精通前端基础外,其他的如后台,运维,linux等都要有所了解。这样你才能对自己所负责的项目有一个整体的把握,不同端开发思维的碰撞,有助于你形成良好的代码习惯,写出高效优质的代码。话不多说,我们开始吧!
6 |
7 | ### 背景
8 |
9 | 这是个学习型的项目,还有些需要优化的地方,项目是参考 [https://github.com/dlyt/YCool](https://github.com/dlyt/YCool) ,利用dva代替redux(个人认为dva比redux好学啊有木有,觉得redux概念不好理解的完全可以从dva入手啊,学完dva,redux秒懂), 然后新增了一些功能。利用工作之余的时间写出来,希望能帮助到大家~
10 |
11 | ### 目录结构
12 | ```
13 | .
14 | ├── wu-server // 后台服务
15 | │ ├── Dockerfile // dockfile
16 | │ ├── README.md
17 | │ ├── bin // 入口文件
18 | │ │ └── server.js
19 | │ ├── config // 配置目录
20 | │ │ ├── env
21 | │ │ ├── index.js
22 | │ │ └── passport.js // 登录认证服务
23 | │ ├── index.js
24 | │ ├── nginx.conf // nginx 配置
25 | │ ├── package-lock.json
26 | │ ├── package.json
27 | │ ├── release.sh // 一键部署shell脚本
28 | │ └── src
29 | │ ├── middleware // 中间件
30 | │ ├── models // mongo model
31 | │ ├── modules // 接口目录
32 | │ └── utils // 公用工具
33 | └── wyfReader // app端
34 | ├── App.js
35 | ├── __tests__
36 | │ └── App.js
37 | ├── app
38 | │ ├── containers // UI组件
39 | │ ├── images
40 | │ ├── index.js
41 | │ ├── libs // 公用库
42 | │ ├── models // dva models
43 | │ ├── router.js // 路由
44 | │ ├── services // 接口服务
45 | │ └── utils
46 | ├── app.json
47 | ├── index.js
48 | ├── jsconfig.json
49 | ├── package-lock.json
50 | ├── package.json
51 | └── yarn.lock
52 | ```
53 | ### 前端
54 |
55 | 即RN项目,仅做了ios端的兼容(偷个懒^-^)。完全版的dva包含了react-router,我们这边不需要,所以只用了[dva-core](https://github.com/dvajs/dva/tree/master/packages/dva-core)
56 |
57 | 基本功能:
58 | >* 小说搜索,动态结果列表显示,支持模糊搜索。
59 | >* 加入书架,阅读,小说删除功能
60 | >* 登录注册功能,node实现验证码
61 |
62 | #### 效果图
63 | 
64 | 
65 | 
66 |
67 | ### 后端
68 |
69 | 框架采用的koa2,passport作为登录认证,cheerio实现爬虫。
70 |
71 | 基本功能:
72 | >* 提供小说操作相关的所有api
73 | >* 提供登录注册相关api
74 | >* node实现svg验证码
75 | >* 定期自动更新小说爬虫
76 |
77 | #### 部署
78 |
79 | 运行`sh release.sh`即可实现一键部署。
80 |
81 | 流行的有两种方案:pm2和docker,现在docker这么流行,咱果断选择它,写了一个自动构建脚本:release.sh
82 |
83 | 大概流程就是: 把代码上传到自己主机上面 > 构建镜像 > 然后以守护进程方式运行。
84 |
85 | 如果还想更近一步的实现自动部署的话,可以试试[travis CI](https://www.travis-ci.org/) 开源免费。它可以通过git的钩子,直接在项目提交到git的时候自动运行构建脚本
86 |
87 | #### nginx 了解一下
88 |
89 |
90 | 它是一个高性能的HTTP和反向代理服务区。学习成本很低,这里咱只是简单的用nginx做了一下代理。
91 | ```
92 | server {
93 | listen 80;
94 | location / {
95 | root /opt/html;
96 | index index.html index.html;
97 | }
98 | location /server/ {
99 | expires -1;
100 | add_header Cache-Control no-cache,no-store;
101 | proxy_pass http://120.79.161.255:8080/;
102 | }
103 | }
104 | ```
105 | 就是原先咱需写端口访问如: http://120.79.161.255:8080/ 现在直接访问http://120.79.161.255/server/ 即可,是不是变漂亮了很多?
106 |
107 | 当然nginx远不止这点功能,比如你可以用它做负载均衡、解决跨域问题、处理缓存 防盗链等HTTP相关问题,处理起来也很容易,只需在配置文件加上相关配置即可,有兴趣朋友可以深入一下。不过话说现在service mesh好像比较流行,Envoy好像想取代它的样子~~哈哈,扯远了~
108 |
109 |
110 | ### 总结
111 |
112 | 总的来说这个项目算是一个全栈练手项目,也没有花多长时间。有些地方还是有点粗糙的,
113 | 但是不妨碍大家学习。还有些概念和工具只是大概提了一下,目的是给初学的朋友留下一个印象(万一今后有用到呢~)。感兴趣的朋友可以基于这个多多优化,加上自己idea。最后,欢迎`star & fork!!!`
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/wu-server/src/modules/users/controller.js:
--------------------------------------------------------------------------------
1 | import Joi from 'joi'
2 | import User from '../../models/users'
3 |
4 | /**
5 | @api {POST} /users/tourists 新增游客
6 | @apiDescription
7 | @apiVersion 1.0.0
8 | @apiName 新增游客
9 | @apiGroup Users
10 | @apiExample Example usage:
11 | curl -H "Content-Type: application/json" -X GET http://localhost:5000/users/tourists
12 |
13 | @apiParam {Object} user 用户对象 (必需)
14 | @apiParam {String} user.uuid ios唯一标识.
15 |
16 | @apiSuccessExample {json} Success-Response:
17 | HTTP/1.1 200 OK
18 | {
19 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU4Yzk0OTk3ZDE4ZTIwMjRiNjYzNjBmYiIsImlhdCI6MTQ4OTYzNjI5M30.OHd22AfwQaUZAuNC2A4w28THizwtf4UgRvWhc3lBuSI"
20 | }
21 | @apiErrorExample {json} Error-Response:
22 | HTTP/1.1 422 Unprocessable Entity
23 | {
24 | "status": 422,
25 | "error": ""
26 | }
27 | */
28 | export async function createTourist (ctx) {
29 | const uuid = ctx.request.body.user.uuid
30 | // 先查
31 | try {
32 | var user = await User.findOne({ uuid: uuid })
33 | } catch (err) {
34 | Handle.sendEmail(err.message)
35 | ctx.throw(422, err.message)
36 | }
37 |
38 | if (user) {
39 | var token = user.generateToken()
40 | } else {
41 | user = new User(ctx.request.body.user)
42 | try {
43 | await user.save()
44 | } catch (err) {
45 | Handle.sendEmail(err.message)
46 | ctx.throw(422, err.message)
47 | }
48 |
49 | var token = user.generateToken()
50 | }
51 |
52 | ctx.body = {
53 | token
54 | }
55 | }
56 |
57 | export async function register (ctx) {
58 | const req = ctx.request.body
59 | const bodySchema = Joi.object().keys({
60 | username: Joi.string().optional(),
61 | password: Joi.string().optional(),
62 | uuid: Joi.string().optional(),
63 | code: Joi.string().optional()
64 | }).unknown()
65 | const result = Joi.validate(req, bodySchema, { allowUnknown: true })
66 | const { username, password, code } = result.value
67 | const captcha = ctx.session.captcha.toLowerCase()
68 | if (captcha !== code.toLowerCase()) {
69 | ctx.body = {
70 | code: -1,
71 | msg: '验证码错误'
72 | }
73 | return
74 | }
75 | delete req.code
76 | // 先查
77 | try {
78 | var user = await User.findOne({ username })
79 | } catch (err) {
80 | Handle.sendEmail(err.message)
81 | ctx.throw(422, err.message)
82 | }
83 | if (user) {
84 | console.log(user, '88888s')
85 | ctx.body = {
86 | code: -1,
87 | msg: '账户已注册'
88 | }
89 | return
90 | } else {
91 | req.uuid = Date.now().toString()
92 | user = new User(req)
93 | try {
94 | await user.save()
95 | } catch (err) {
96 | Handle.sendEmail(err.message)
97 | ctx.throw(425, err.message)
98 | }
99 |
100 | var token = user.generateToken()
101 | }
102 |
103 | ctx.body = {
104 | code: 0,
105 | token,
106 | username
107 | }
108 | }
109 |
110 | export async function getCaptcha (ctx) {
111 | const svgCaptcha = require('svg-captcha')
112 | const captcha = svgCaptcha.create({
113 | width: 80,
114 | height: 40,
115 | ignoreChars: '0o1i'
116 | })
117 | if (captcha.data) {
118 | ctx.session.captcha = captcha.text
119 | ctx.type = 'svg'
120 | ctx.body = {
121 | code: 0,
122 | response: captcha.data
123 | }
124 | } else {
125 | ctx.throw(422, '生成验证码失败')
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/wyfReader/app/utils/utils.js:
--------------------------------------------------------------------------------
1 | /*!
2 | *
3 | * Util模块 React Native module
4 | * 主要提供工具方法
5 | *
6 | */
7 | import React, { Compoent } from 'react';
8 | import { PixelRatio, ActivityIndicatorIOS, Dimensions} from "react-native";
9 |
10 | module.exports = {
11 | navigationHeight: 44,
12 | navigationBarBGColor: "#3497FF",
13 | statusBarHeight: 20,
14 | /*最小线宽*/
15 | pixel: 1 / PixelRatio.get(),
16 |
17 | /*屏幕尺寸*/
18 | size: {
19 | width: Dimensions.get("window").width,
20 | height: Dimensions.get("window").height
21 | },
22 | handleContent: function(content) {// 逐字逐句拆分
23 | const length = content.length;
24 | let array = [];
25 | let x = 0,
26 | y,
27 | m = 0;
28 | while (x < length) {
29 | let _array = [];
30 | for (let i = 0; i <= 16; i++) {
31 | let str_spa = content.substring(x, x + 1);
32 | let str_sto = content.substring(x, x + 18);
33 | const re = /^\s+$/;
34 | if (str_sto.indexOf("”") != -1) {
35 | y = x + str_sto.indexOf("”") + 1;
36 | _array[i] = content.substring(x, y);
37 | x = y;
38 | continue;
39 | } else if (str_sto.indexOf("。") != -1) {
40 | y = x + str_sto.indexOf("。") + 1;
41 | if (re.exec(content.substring(y, y + 1))) {
42 | y = x + str_sto.indexOf("。") + 1;
43 | _array[i] = content.substring(x, y);
44 | x = y;
45 | continue;
46 | } else {
47 | if (str_sto.indexOf("!") != -1) {
48 | y = x + str_sto.indexOf("!") + 1;
49 | _array[i] = content.substring(x, y);
50 | x = y;
51 | continue;
52 | } else {
53 | y = x + 18;
54 | _array[i] = content.substring(x, y);
55 | x = y;
56 | continue;
57 | }
58 | }
59 | } else if (str_sto.indexOf("!") != -1) {
60 | y = x + str_sto.indexOf("!") + 1;
61 | if (re.exec(content.substring(y, y + 1))) {
62 | y = x + str_sto.indexOf("!") + 1;
63 | _array[i] = content.substring(x, y);
64 | x = y;
65 | continue;
66 | } else {
67 | y = x + 18;
68 | _array[i] = content.substring(x, y);
69 | x = y;
70 | continue;
71 | }
72 | } else if (str_sto.indexOf("?") != -1) {
73 | y = x + str_sto.indexOf("?") + 1;
74 | if (re.exec(content.substring(y, y + 1))) {
75 | y = x + str_sto.indexOf("?") + 1;
76 | _array[i] = content.substring(x, y);
77 | x = y;
78 | continue;
79 | } else {
80 | y = x + 18;
81 | _array[i] = content.substring(x, y);
82 | x = y;
83 | continue;
84 | }
85 | } else if (re.exec(str_spa)) {
86 | y = x + 24;
87 | if (content.substring(x, y).indexOf("。") != -1) {
88 | y = x + content.substring(x, y).indexOf("。") + 1;
89 | _array[i] = content.substring(x, y);
90 | x = y;
91 | continue;
92 | }
93 | _array[i] = content.substring(x, y);
94 | x = y;
95 | continue;
96 | } else {
97 | y = x + 18;
98 | _array[i] = content.substring(x, y);
99 | x = y;
100 | }
101 | }
102 | array[m] = _array;
103 | m++;
104 | }
105 | // console.log((m - 1) * 375);
106 | return array;
107 | },
108 | /*loading效果*/
109 | // loading:
110 | };
111 |
--------------------------------------------------------------------------------
/wu-server/src/utils/crawler.js:
--------------------------------------------------------------------------------
1 | import http from 'http'
2 | import Request from 'request'
3 | import cheerio from 'cheerio'
4 | import iconv from 'iconv-lite'
5 | import Chapter from '../models/chapters'
6 | import config from '../../config'
7 |
8 | export function getSearchLists (url) {
9 | return new Promise(function (resolve, reject) {
10 | Request(url, function (err, res, body) {
11 | if (!err) {
12 | let results = []
13 |
14 | const $ = cheerio.load(body)
15 |
16 | const list = $('.result-item-title a')
17 |
18 | for (let i = 0, len = list.length; i < len; i++) {
19 | let name = list[i].attribs.title
20 | let url = list[i].attribs.href
21 |
22 | results.push({
23 | name: name,
24 | url: url
25 | })
26 | }
27 | resolve(results)
28 | } else {
29 | reject(err)
30 | }
31 | })
32 | })
33 | }
34 |
35 | export async function getNovel ($, id, url) {
36 | const list = $('#list dd a')
37 | let chapters = []
38 | for (let i = 0, len = list.length; i < len; i++) {
39 | let title = list[i].children[0].data
40 | let href = config.crawlerUrl + '/' + list[i].attribs.href
41 | chapters.push({
42 | title: title,
43 | postfix: href,
44 | number: i,
45 | content: ''
46 | })
47 | }
48 | let chapter = new Chapter({
49 | novel: id,
50 | chapters
51 | })
52 | await chapter.save()
53 | }
54 |
55 | export function getChapterContent (url) {
56 | return new Promise(function (resolve, reject) {
57 | Request({url: url, encoding: null}, function (err, res, body) {
58 | if (!err) {
59 | const html = iconv.decode(body, 'utf-8')
60 | const $ = cheerio.load(html, {decodeEntities: false})
61 | const content = $('#content').text()
62 | resolve(content)
63 | } else {
64 | reject(err)
65 | }
66 | })
67 | })
68 | }
69 |
70 | export function getHtml (url) {
71 | return new Promise(function (resolve, reject) {
72 | Request({url: url, encoding: null}, function (err, res, body) {
73 | if (!err) {
74 | const html = iconv.decode(body, 'utf-8')
75 | const $ = cheerio.load(html, {decodeEntities: false})
76 | resolve($)
77 | } else {
78 | console.log(err)
79 | reject(err)
80 | }
81 | })
82 | })
83 | }
84 |
85 | export function getBody (url) {
86 | return new Promise(function (resolve, reject) {
87 | Request({url: url, encoding: null}, function (err, res, body) {
88 | if (!err) {
89 | const _body = iconv.decode(body, 'utf-8')
90 | resolve(_body)
91 | } else {
92 | reject(err)
93 | }
94 | })
95 | })
96 | }
97 |
98 | export function request (url) {
99 | return new Promise(function (resolve, reject) {
100 | http.get(url, function (res) {
101 | let body = ''
102 |
103 | res.on('data', function (data) {
104 | body += data
105 | })
106 | res.on('end', function () {
107 | resolve(body)
108 | })
109 | }).on('error', function (e) {
110 | reject(e)
111 | })
112 | })
113 | }
114 |
115 | async function saveChapters (url, list, first, last) {
116 | for (let i = first; i < last; i++) {
117 | let title = list[i].children[0].data
118 | let href = list[i].attribs.href
119 | let num = href.substring(0, href.length - 5)
120 | let contentUrl = `${url}${href}`
121 | let content = await getContent(contentUrl)
122 |
123 | let chapter = new Chapter({
124 | title: title,
125 | number: num,
126 | content: content
127 | })
128 |
129 | await chapter.save()
130 | }
131 |
132 | return true
133 | }
134 |
--------------------------------------------------------------------------------
/wyfReader/ios/wyfReader/Base.lproj/LaunchScreen.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/wyfReader/app/router.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import { BackHandler, Animated, Easing } from 'react-native'
3 | import {
4 | StackNavigator,
5 | TabNavigator,
6 | TabBarBottom,
7 | addNavigationHelpers,
8 | NavigationActions,
9 | } from 'react-navigation'
10 | import { connect } from 'react-redux'
11 |
12 | import Login from './containers/Login'
13 | import Register from './containers/Register'
14 | import Home from './containers/Home'
15 | import Account from './containers/Account'
16 | import Detail from './containers/Detail'
17 | import Search from './containers/Search'
18 | import Reader from './containers/Reader'
19 | import Directory from './containers/Directory'
20 |
21 | const HomeNavigator = TabNavigator(
22 | {
23 | Home: { screen: Home },
24 | Account: { screen: Account },
25 | },
26 | {
27 | tabBarComponent: TabBarBottom,
28 | tabBarPosition: 'bottom',
29 | swipeEnabled: false,
30 | animationEnabled: false,
31 | lazyLoad: true,
32 | }
33 | )
34 |
35 | const MainNavigator = StackNavigator(
36 | {
37 | HomeNavigator: { screen: HomeNavigator },
38 | Detail: { screen: Detail },
39 | Search: { screen: Search },
40 | Reader: { screen: Reader },
41 | Directory: { screen: Directory },
42 | },
43 | {
44 | headerMode: 'none',
45 | }
46 | )
47 |
48 | const AppNavigator = StackNavigator(
49 | {
50 | Main: { screen: MainNavigator },
51 | Login: { screen: Login },
52 | Register: { screen: Register },
53 | },
54 | {
55 | headerMode: 'none',
56 | mode: 'modal',
57 | navigationOptions: {
58 | gesturesEnabled: false,
59 | },
60 | transitionConfig: () => ({
61 | transitionSpec: {
62 | duration: 300,
63 | easing: Easing.out(Easing.poly(4)),
64 | timing: Animated.timing,
65 | },
66 | screenInterpolator: sceneProps => {
67 | const { layout, position, scene } = sceneProps
68 | const { index } = scene
69 |
70 | const height = layout.initHeight
71 | const translateY = position.interpolate({
72 | inputRange: [index - 1, index, index + 1],
73 | outputRange: [height, 0, 0],
74 | })
75 |
76 | const opacity = position.interpolate({
77 | inputRange: [index - 1, index - 0.99, index],
78 | outputRange: [0, 1, 1],
79 | })
80 |
81 | return { opacity, transform: [{ translateY }] }
82 | },
83 | }),
84 | }
85 | )
86 |
87 | function getCurrentScreen(navigationState) {
88 | if (!navigationState) {
89 | return null
90 | }
91 | const route = navigationState.routes[navigationState.index]
92 | if (route.routes) {
93 | return getCurrentScreen(route)
94 | }
95 | return route.routeName
96 | }
97 |
98 | @connect(({ router }) => ({ router }))
99 | class Router extends PureComponent {
100 | componentWillMount() {
101 | BackHandler.addEventListener('hardwareBackPress', this.backHandle)
102 | }
103 |
104 | componentWillUnmount() {
105 | BackHandler.removeEventListener('hardwareBackPress', this.backHandle)
106 | }
107 |
108 | backHandle = () => {
109 | const currentScreen = getCurrentScreen(this.props.router)
110 | if (currentScreen === 'Login') {
111 | return true
112 | }
113 | if (currentScreen !== 'Home') {
114 | this.props.dispatch(NavigationActions.back())
115 | return true
116 | }
117 | return false
118 | }
119 |
120 | render() {
121 | const { dispatch, router } = this.props
122 | const navigation = addNavigationHelpers({ dispatch, state: router })
123 | return
124 | }
125 | }
126 |
127 | export function routerReducer(state, action = {}) {
128 | return AppNavigator.router.getStateForAction(action, state)
129 | }
130 |
131 | export default Router
132 |
--------------------------------------------------------------------------------
/wyfReader/app/containers/Directory.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { StyleSheet, View, Image, Text, ListView, TouchableOpacity, AsyncStorage } from 'react-native'
3 | import { connect } from 'react-redux'
4 |
5 | import Util from "../utils/utils";
6 | import { NavigationActions } from "../utils";
7 |
8 | @connect((state) => ({
9 | directory: state.reader.directory
10 | }))
11 | class Directory extends Component {
12 | constructor(props) {
13 | super(props)
14 | this.state = {
15 | order: 1,
16 | dataSource: new ListView.DataSource({
17 | rowHasChanged: (row1, row2) => row1 !== row2
18 | }),
19 | }
20 | this.id = this.props.navigation.state.params.id;
21 | this.name = this.props.navigation.state.params.name;
22 | }
23 | componentWillMount() {
24 | this.props.dispatch({
25 | type:"reader/getDirectory",
26 | payload: {
27 | id: this.id,
28 | order: this.state.order
29 | }
30 | })
31 | }
32 | changeOrder() {
33 | if (this.state.order === 1) {
34 | this.setState({order: -1})
35 | }
36 | else {
37 | this.setState({order: 1})
38 | }
39 | this.props.dispatch({
40 | type:"reader/getDirectory",
41 | payload: {
42 | id: this.id,
43 | order: this.state.order
44 | }
45 | })
46 | }
47 | goReader(num) {
48 | this.props.dispatch(NavigationActions.navigate({ routeName: 'Reader', params: {id: this.id, name: this.name, num} }))
49 | }
50 | renderRow(item) {
51 | return(
52 | {this.goReader(item.number)} }>
56 |
57 |
58 | {item.number + 1}.
59 |
60 |
61 |
62 |
63 | {item.title}
64 |
65 |
66 |
67 | )
68 | }
69 | renderList(lists) {
70 | return(
71 |
76 | )
77 | }
78 | render() {
79 | const lists = this.props.directory
80 | return (
81 |
82 |
83 |
84 | {this.props.dispatch(NavigationActions.back())}}
86 | style={styles.button}
87 | >
88 |
91 |
92 |
93 |
94 | {this.name}
95 |
96 |
97 | {this.changeOrder()}}>
100 | {this.state.order === -1 ? : }
103 |
104 |
105 |
106 | {this.renderList(lists)}
107 |
108 | )
109 | }
110 | }
111 |
112 | const styles = StyleSheet.create({
113 | scene: {
114 | flex: 1,
115 | },
116 | nav: {
117 | backgroundColor: 'rgb(52,120,246)',
118 | flexDirection: 'row',
119 | alignItems: 'center',
120 | zIndex: 99
121 | },
122 | button: {
123 | width: 44,
124 | height: 44,
125 | justifyContent: 'center',
126 | alignItems: 'center',
127 | },
128 | title: {
129 | flex: 1,
130 | height: 66,
131 | justifyContent: 'center',
132 | },
133 | titleText: {
134 | marginTop: 25,
135 | fontSize: 18,
136 | textAlign: 'center',
137 | color: '#ffffff',
138 | },
139 | leftButton: {
140 | marginTop: 25,
141 | marginRight: 5,
142 | },
143 | item: {
144 | height: 60,
145 | borderBottomWidth: Util.pixel,
146 | borderColor: '#A5A5A5',
147 | },
148 | row:{
149 | flexDirection: 'row',
150 | },
151 | rightImg: {
152 | marginTop: 20
153 | }
154 | })
155 |
156 | export default Directory
157 |
--------------------------------------------------------------------------------
/wyfReader/app/models/app.js:
--------------------------------------------------------------------------------
1 | import { Toast } from 'antd-mobile'
2 | import { AsyncStorage } from 'react-native'
3 | import { createAction, NavigationActions } from '../utils'
4 | import * as authService from '../services/auth'
5 | import * as services from '../services/app'
6 |
7 | export default {
8 | namespace: 'app',
9 | state: {
10 | },
11 | reducers: {
12 | loginStart(state, { payload: { loginData } }) {
13 | return { ...state, loginData }
14 | },
15 | receiveRegister(state, { payload: { registerData } }) {
16 | return { ...state, registerData }
17 | },
18 | receiveBookList(state, { payload: { bookList } }){
19 | return { ...state, bookList }
20 | },
21 | receiveToken(state, { payload: { token } }){
22 | return { ...state, token }
23 | },
24 | receiveBookName(state, { payload: { bookNames } }){
25 | return { ...state, bookNames }
26 | },
27 | receiveSearchNovel(state, { payload: { searchBooks } }){
28 | return { ...state, searchBooks }
29 | },
30 | receiveNovelInfo(state, { payload: { NovelInfo } }){
31 | return { ...state, NovelInfo }
32 | },
33 |
34 | },
35 | effects: {
36 | *register({ payload: { registerData } }, { call, put }) {
37 | // yield put(createAction('loginStart')())
38 | const { data } = yield call(services.register, registerData)
39 | yield put({
40 | type: "receiveRegister",
41 | payload: {
42 | registerData: data.data
43 | }
44 | })
45 | if (data.data.code === 0) {
46 | yield put(
47 | NavigationActions.reset({
48 | index: 0,
49 | actions: [NavigationActions.navigate({ routeName: 'Login' })],
50 | })
51 | )
52 | }
53 | },
54 | *login({ payload: { loginData } }, { call, put }) {
55 | const { data } = yield call(services.login, loginData)
56 | yield put({
57 | type: "loginStart",
58 | payload: {
59 | loginData: data.data
60 | }
61 | })
62 | yield put({
63 | type: "receiveToken",
64 | payload: {
65 | token: data.data.token
66 | }
67 | })
68 |
69 | if (data.data.code === 0) {
70 | // 更新书架
71 | yield put({
72 | type: "getBookList",
73 | payload: {}
74 | })
75 | yield put(
76 | NavigationActions.reset({
77 | index: 0,
78 | actions: [NavigationActions.navigate({ routeName: 'Main' })],
79 | })
80 | )
81 | }
82 | },
83 | *getBookList({}, { call, put, select }) {
84 | // 从本地缓存中拿到token
85 | const token = yield AsyncStorage.getItem(`userToken`)
86 | if (!token) {
87 | yield put({
88 | type: 'getAuth',
89 | payload: {}
90 | })
91 | return
92 | }
93 | const { data } = yield call(services.getBookshelfTourist, token)
94 | yield put({
95 | type:'receiveBookList',
96 | payload: {
97 | bookList: data.data.list,
98 | }
99 | })
100 | },
101 | *getNovelList({ payload: { name } }, { call, put }){
102 | const { data } = yield call(services.getNovelList, name)
103 | yield put({
104 | type:'receiveSearchNovel',
105 | payload: {
106 | searchBooks: data.data.response,
107 | }
108 | })
109 | },
110 | *getNovelInfo({ payload: { name, url } }, { call, put }){
111 | const { data } = yield call(services.getNovelInfo, name, url)
112 | if (!data) {
113 | Toast.fail('服务器错误!', 2)
114 | }
115 | let novelInfo;
116 | if (data.data.code === 404) {
117 | novelInfo = {}
118 | } else {
119 | novelInfo = data.data.novelInfo
120 | }
121 |
122 | yield put({
123 | type:'receiveNovelInfo',
124 | payload: {
125 | NovelInfo: novelInfo,
126 | }
127 | })
128 | },
129 | // 加入书架
130 | *orderNovel ({ payload: { id } }, { call, put }) {
131 | yield call(services.orderNovel, id)
132 | // 更新书架
133 | yield put({
134 | type: 'getBookList',
135 | payload: {}
136 | })
137 | },
138 | *deleteNovel({ payload: {id} },{ call, put }) {
139 | console.log(id, 2222)
140 | yield call(services.deleteNovel, id)
141 | yield put({
142 | type: 'getBookList'
143 | })
144 | },
145 | *searchBookWords({ payload: { text } }, { call, put }) {
146 | const { data } = yield call(services.searchBookWords, text)
147 | yield put({
148 | type:'receiveBookName',
149 | payload: {
150 | bookNames: data.data.response,
151 | }
152 | })
153 | },
154 | *getAuth({ payload:{} }, { call, put }) {
155 | const {data} = yield call(services.getAuth)
156 | yield put({
157 | type: 'receiveToken',
158 | payload: {
159 | token: data.data.token
160 | }
161 | })
162 | yield put({
163 | type: 'getBookList',
164 | payload: {
165 |
166 | }
167 | })
168 | },
169 |
170 | },
171 | }
172 |
--------------------------------------------------------------------------------
/wyfReader/ios/wyfReader.xcodeproj/xcshareddata/xcschemes/wyfReader.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
61 |
67 |
68 |
69 |
70 |
71 |
77 |
78 |
79 |
80 |
81 |
82 |
92 |
94 |
100 |
101 |
102 |
103 |
104 |
105 |
111 |
113 |
119 |
120 |
121 |
122 |
124 |
125 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/wyfReader/ios/wyfReader.xcodeproj/xcshareddata/xcschemes/wyfReader-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
61 |
67 |
68 |
69 |
70 |
71 |
77 |
78 |
79 |
80 |
81 |
82 |
92 |
94 |
100 |
101 |
102 |
103 |
104 |
105 |
111 |
113 |
119 |
120 |
121 |
122 |
124 |
125 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/wyfReader/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/wu-server/src/modules/bookshelves.js/controller.js:
--------------------------------------------------------------------------------
1 | import Bookshelf from '../../models/bookshelfs'
2 | import Chapter from '../../models/chapters'
3 | import Novel from '../../models/novels'
4 |
5 | /**
6 | @api {GET} /bookshelfs 获取书架列表
7 | @apiPermission User
8 | @apiVersion 1.0.0
9 | @apiName 获取书架列表
10 | @apiGroup Bookshelfs
11 |
12 | @apiExample Example usage:
13 | curl -H "Content-Type: application/json" "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU4YjhmZDRkODUyYTE1YzliNmYyNjI3MSIsImlhdCI6MTQ4ODU1MTc2N30.IEgYwmgyqOBft9s38ool7cmuC2yIlWYVLf4WQzcbqAI" -X GET localhost:5000/bookshelfs
14 |
15 | @apiSuccessExample {json} Success-Response:
16 | HTTP/1.1 200 OK
17 | {
18 | "list": [
19 | {
20 | "_id": "58c949c5d18e2024b66360fc",
21 | "user": "58c94997d18e2024b66360fb",
22 | "novel": {
23 | "_id": "58c4cb509e4dad30f80d2f84",
24 | "url": "http://www.37zw.com/3/3960/",
25 | "name": "1852铁血中华",
26 | "author": "绯红之月",
27 | "updateTime": "2017-03-12",
28 | "introduction": " 1852,是革命,或者是一场该改朝换代的改良。燃烧的铁与血,最终能创造一个什么样的未来?\n",
29 | "__v": 0,
30 | "countChapter": "1377",
31 | "lastChapterTitle": "第644章 剪影 3",
32 | "img": "http://www.37zw.com/d/image/3/3960/3960s.jpg"
33 | },
34 | "chapter": {
35 | "_id": "58c4cb509e4dad30f80d2f85",
36 | "number": 0
37 | },
38 | "__v": 0
39 | }
40 | ]
41 | }
42 |
43 | @apiErrorExample {json} Error-Response:
44 | HTTP/1.1 422 Unprocessable Entity
45 | {
46 | "status": 422,
47 | "error": ""
48 | }
49 | */
50 | export async function getBookshelf (ctx) {
51 | const user = ctx.state.user
52 | try {
53 | var list = await Bookshelf.getList(user.id)
54 | } catch (e) {
55 | Handle.sendEmail(e.message)
56 | ctx.throw(422, e.message)
57 | }
58 |
59 | ctx.body = {
60 | list: list
61 | }
62 | }
63 |
64 | /**
65 | @api {POST} /bookshelfs/order 订阅小说
66 | @apiPermission User
67 | @apiVersion 1.0.0
68 | @apiName 订阅小说
69 | @apiGroup Bookshelfs
70 |
71 | @apiExample Example usage:
72 | curl -H "Content-Type: application/json" "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU4YjhmZDRkODUyYTE1YzliNmYyNjI3MSIsImlhdCI6MTQ4ODU1MTc2N30.IEgYwmgyqOBft9s38ool7cmuC2yIlWYVLf4WQzcbqAI" -X GET localhost:5000/bookshelfs/order
73 |
74 | @apiParam {String} id 小说ID.
75 |
76 | @apiSuccessExample {json} Success-Response:
77 | HTTP/1.1 200 OK
78 | {
79 | "success": true
80 | }
81 |
82 | @apiErrorExample {json} Error-Response:
83 | HTTP/1.1 422 Unprocessable Entity
84 | {
85 | "status": 422,
86 | "error": ""
87 | }
88 | */
89 | export async function orderNovel (ctx) {
90 | const user = ctx.state.user
91 | const novelId = ctx.request.body.id
92 |
93 | try {
94 | var titles = await Chapter.getTitles(novelId)
95 |
96 | } catch (err) {
97 | ctx.throw(422, err.message)
98 | }
99 | const chapterId = titles[0]._id
100 | const bookshelf = new Bookshelf({
101 | user: user.id,
102 | novel: novelId,
103 | chapter: chapterId,
104 | number: 0
105 | })
106 |
107 | try {
108 | await bookshelf.save()
109 | } catch (e) {
110 | Handle.sendEmail(e.message)
111 | ctx.throw(422, e.message)
112 | }
113 |
114 | ctx.body = {
115 | success: true
116 | }
117 | }
118 |
119 | /**
120 | @api {POST} /bookshelfs/delect 取消订阅
121 | @apiVersion 1.0.0
122 | @apiName 取消订阅
123 | @apiGroup Bookshelfs
124 |
125 | @apiExample Example usage:
126 | curl -H "Content-Type: application/json" -X GET localhost:5000/bookshelfs/delect
127 |
128 | @apiParam {String} id 小说ID.
129 |
130 | @apiSuccessExample {json} Success-Response:
131 | HTTP/1.1 200 OK
132 | {
133 | "success": true
134 | }
135 |
136 | @apiErrorExample {json} Error-Response:
137 | HTTP/1.1 422 Unprocessable Entity
138 | {
139 | "status": 422,
140 | "error": ""
141 | }
142 | */
143 | export async function delectNovel (ctx) {
144 | const id = ctx.request.body.id
145 | try {
146 | await Bookshelf.remove({_id: id})
147 | } catch (e) {
148 | Handle.sendEmail(e.message)
149 | ctx.throw(422, err.message)
150 | }
151 |
152 | ctx.body = {
153 | success: true
154 | }
155 | }
156 |
157 | /**
158 | @api {POST} /bookshelfs 记录最后阅读章节
159 | @apiPermission User
160 | @apiVersion 1.0.0
161 | @apiName 记录最后阅读章节
162 | @apiGroup Bookshelfs
163 |
164 | @apiExample Example usage:
165 | curl -H "Content-Type: application/json" "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU4YjhmZDRkODUyYTE1YzliNmYyNjI3MSIsImlhdCI6MTQ4ODU1MTc2N30.IEgYwmgyqOBft9s38ool7cmuC2yIlWYVLf4WQzcbqAI" -X GET localhost:5000/bookshelfs/change
166 |
167 | @apiParam num 小说章节
168 | @apiParam x 页数
169 |
170 | @apiSuccessExample {json} Success-Response:
171 | HTTP/1.1 200 OK
172 |
173 | @apiErrorExample {json} Error-Response:
174 | HTTP/1.1 422 Unprocessable Entity
175 | {
176 | "status": 422,
177 | "error": ""
178 | }
179 | */
180 | export async function changeBookshelf (ctx) {
181 | let chapter,bookshelf
182 | const user = ctx.state.user
183 | const novel = ctx.request.body.novel
184 | const progress = novel.x / 375
185 | const options = {
186 | userId: user.id,
187 | novelId: novel.id
188 | }
189 | try {
190 | bookshelf = await Bookshelf.findByUserAndNovelId(options)
191 | chapter = await Chapter.findByNumber(novel.id, novel.num)
192 | } catch (e) {
193 | Handle.sendEmail(e.message)
194 | ctx.throw(422, e.message)
195 | }
196 |
197 | bookshelf.progress = progress
198 | bookshelf.chapter = chapter.id
199 | try {
200 | await bookshelf.save()
201 | } catch (e) {
202 | Handle.sendEmail(e.message)
203 | ctx.throw(422, e.message)
204 | }
205 |
206 | ctx.body = {
207 | success: true
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/wyfReader/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.android.application"
2 |
3 | import com.android.build.OutputFile
4 |
5 | /**
6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
7 | * and bundleReleaseJsAndAssets).
8 | * These basically call `react-native bundle` with the correct arguments during the Android build
9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
10 | * bundle directly from the development server. Below you can see all the possible configurations
11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the
12 | * `apply from: "../../node_modules/react-native/react.gradle"` line.
13 | *
14 | * project.ext.react = [
15 | * // the name of the generated asset file containing your JS bundle
16 | * bundleAssetName: "index.android.bundle",
17 | *
18 | * // the entry file for bundle generation
19 | * entryFile: "index.android.js",
20 | *
21 | * // whether to bundle JS and assets in debug mode
22 | * bundleInDebug: false,
23 | *
24 | * // whether to bundle JS and assets in release mode
25 | * bundleInRelease: true,
26 | *
27 | * // whether to bundle JS and assets in another build variant (if configured).
28 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
29 | * // The configuration property can be in the following formats
30 | * // 'bundleIn${productFlavor}${buildType}'
31 | * // 'bundleIn${buildType}'
32 | * // bundleInFreeDebug: true,
33 | * // bundleInPaidRelease: true,
34 | * // bundleInBeta: true,
35 | *
36 | * // whether to disable dev mode in custom build variants (by default only disabled in release)
37 | * // for example: to disable dev mode in the staging build type (if configured)
38 | * devDisabledInStaging: true,
39 | * // The configuration property can be in the following formats
40 | * // 'devDisabledIn${productFlavor}${buildType}'
41 | * // 'devDisabledIn${buildType}'
42 | *
43 | * // the root of your project, i.e. where "package.json" lives
44 | * root: "../../",
45 | *
46 | * // where to put the JS bundle asset in debug mode
47 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
48 | *
49 | * // where to put the JS bundle asset in release mode
50 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release",
51 | *
52 | * // where to put drawable resources / React Native assets, e.g. the ones you use via
53 | * // require('./image.png')), in debug mode
54 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
55 | *
56 | * // where to put drawable resources / React Native assets, e.g. the ones you use via
57 | * // require('./image.png')), in release mode
58 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
59 | *
60 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means
61 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
62 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle
63 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
64 | * // for example, you might want to remove it from here.
65 | * inputExcludes: ["android/**", "ios/**"],
66 | *
67 | * // override which node gets called and with what additional arguments
68 | * nodeExecutableAndArgs: ["node"],
69 | *
70 | * // supply additional arguments to the packager
71 | * extraPackagerArgs: []
72 | * ]
73 | */
74 |
75 | project.ext.react = [
76 | entryFile: "index.js"
77 | ]
78 |
79 | apply from: "../../node_modules/react-native/react.gradle"
80 |
81 | /**
82 | * Set this to true to create two separate APKs instead of one:
83 | * - An APK that only works on ARM devices
84 | * - An APK that only works on x86 devices
85 | * The advantage is the size of the APK is reduced by about 4MB.
86 | * Upload all the APKs to the Play Store and people will download
87 | * the correct one based on the CPU architecture of their device.
88 | */
89 | def enableSeparateBuildPerCPUArchitecture = false
90 |
91 | /**
92 | * Run Proguard to shrink the Java bytecode in release builds.
93 | */
94 | def enableProguardInReleaseBuilds = false
95 |
96 | android {
97 | compileSdkVersion 26
98 | buildToolsVersion '26.0.2'
99 |
100 | defaultConfig {
101 | applicationId "com.wyfreader"
102 | minSdkVersion 16
103 | targetSdkVersion 22
104 | versionCode 1
105 | versionName "1.0"
106 | ndk {
107 | abiFilters "armeabi-v7a", "x86"
108 | }
109 | }
110 | splits {
111 | abi {
112 | reset()
113 | enable enableSeparateBuildPerCPUArchitecture
114 | universalApk false // If true, also generate a universal APK
115 | include "armeabi-v7a", "x86"
116 | }
117 | }
118 | buildTypes {
119 | release {
120 | minifyEnabled enableProguardInReleaseBuilds
121 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
122 | }
123 | }
124 | // applicationVariants are e.g. debug, release
125 | applicationVariants.all { variant ->
126 | variant.outputs.each { output ->
127 | // For each separate APK per architecture, set a unique version code as described here:
128 | // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
129 | def versionCodes = ["armeabi-v7a":1, "x86":2]
130 | def abi = output.getFilter(OutputFile.ABI)
131 | if (abi != null) { // null for the universal-debug, universal-release variants
132 | output.versionCodeOverride =
133 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
134 | }
135 | }
136 | }
137 | }
138 |
139 | dependencies {
140 | compile project(':react-native-svg')
141 | compile fileTree(dir: "libs", include: ["*.jar"])
142 | compile "com.android.support:appcompat-v7:26.0.2"
143 | compile "com.facebook.react:react-native:+" // From node_modules
144 | }
145 |
146 | // Run this once to be able to run the application with BUCK
147 | // puts all compile dependencies into folder libs for BUCK to use
148 | task copyDownloadableDepsToLibs(type: Copy) {
149 | from configurations.compile
150 | into 'libs'
151 | }
152 |
--------------------------------------------------------------------------------
/wyfReader/app/containers/Register.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import {
3 | StyleSheet,
4 | View,
5 | Text,
6 | Button,
7 | Image,
8 | TouchableOpacity,
9 | ActivityIndicator
10 | } from "react-native";
11 | import { Svg, Path, Circle } from "react-native-svg";
12 | import { connect } from "react-redux";
13 | import { List, InputItem } from "antd-mobile";
14 | import { createForm } from "rc-form";
15 | import { createAction, NavigationActions } from "../utils";
16 | import Util from "../utils/utils";
17 | import Request from "../libs/request";
18 |
19 | @connect((state) => ({registerData: state.app.registerData}))
20 | class Register extends Component {
21 | static navigationOptions = {
22 | title: "Login"
23 | };
24 | constructor(props) {
25 | super(props);
26 | this.state = {
27 | msg: "在这里登录!",
28 | paths: []
29 | };
30 | this.headStyle = [styles.header];
31 | }
32 | componentWillMount() {
33 | Request.get("/users/captcha").then(res => {
34 | if (res.code === 0) {
35 | let arr = res.response.replace(/>|<").split("|");
36 | let paths = arr.slice(1, arr.length - 1);
37 | let results = this.handlePaths(paths);
38 | this.setState({
39 | paths: results
40 | });
41 | }
42 | });
43 | }
44 | handlePaths = paths => {
45 | let results = paths.map((v, k) => {
46 | let path = {};
47 | let d = v.match(/(d="[^a-z]+")/);
48 | let fill = v.match(/(fill="[^\s]+")/);
49 | let stroke = v.match(/(stroke="[^\s]+")/);
50 |
51 | path.d = d ? d[0].split("=")[1].replace(/"/g, "") : "";
52 | path.fill = fill ? fill[0].split("=")[1].replace(/"/g, "") : "none";
53 | path.stroke = stroke ? stroke[0].split("=")[1].replace(/"/g, "") : "none";
54 | return ;
55 | });
56 | return results;
57 | };
58 | getCaptcha() {
59 | Request.get("/users/captcha").then(res => {
60 | if (res.code === 0) {
61 | let arr = res.response.replace(/>|<").split("|");
62 | let paths = arr.slice(1, arr.length - 1);
63 | let results = this.handlePaths(paths);
64 | this.setState({
65 | paths: results
66 | });
67 | }
68 | });
69 | }
70 | // onLogin = () => {
71 | // this.props.dispatch(createAction("app/login")());
72 | // };
73 |
74 | onClose = () => {
75 | this.props.dispatch(NavigationActions.back());
76 | };
77 | handleClick = () => {
78 | const { validateFields } = this.props.form;
79 | validateFields((err, value) => {
80 | if (err) {
81 | this.headStyle = [styles.header, styles.err];
82 | let msg
83 | if (err.code) {
84 | msg = "请输入验证码!"
85 | }
86 | if (err.username || err.password || err.confirm) {
87 | msg = "请输入账号和密码!"
88 | }
89 | alert(msg)
90 | this.getCaptcha()
91 | } else {
92 | const { username, password, confirm, code } = value;
93 | if (password !== confirm) {
94 | alert('两次输入密码不一致,请重新输入')
95 | return;
96 | }
97 | console.log(this.props)
98 | this.props.dispatch({
99 | type: 'app/register',
100 | payload: {
101 | registerData: {
102 | password,
103 | username,
104 | code
105 | }
106 | }
107 | }).then(() => {
108 | const registerData = this.props.registerData;
109 | console.log(registerData,88)
110 | if (registerData.code === -1) {
111 | alert(registerData.msg)
112 | this.getCaptcha()
113 | }
114 | })
115 | }
116 |
117 | });
118 | };
119 | render() {
120 | const { getFieldProps } = this.props.form;
121 | return (
122 |
123 | {/* {fetching
124 | ?
125 | : }
126 | {!fetching && } */}
127 |
130 |
138 | 账号
139 |
140 |
149 | 密码
150 |
151 |
152 |
161 | 确认密码
162 |
163 |
164 |
165 |
166 |
175 | 验证码
176 |
177 |
178 |
179 | {
182 | this.getCaptcha()
183 | }}
184 | >
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 | {
195 | this.onClose()
196 | }}>
197 | 返回
198 |
199 |
200 |
201 | );
202 | }
203 | }
204 |
205 | const styles = StyleSheet.create({
206 | container: {
207 | flex: 1,
208 | justifyContent: "center"
209 | },
210 | header: {
211 | padding: 10,
212 | color: "#999"
213 | },
214 | err: {
215 | color: "#f00"
216 | },
217 | code: {
218 | flex: 0.6
219 | },
220 | codeWrap: {
221 | flex: 0.4,
222 | borderBottomWidth: Util.pixel,
223 | borderBottomColor: "#ddd",
224 | alignItems: "center",
225 | justifyContent: "center"
226 | },
227 | codeimg: {
228 | width: 80,
229 | height: 40
230 | },
231 | register: {
232 | color: '#007aff',
233 | textAlign: 'right',
234 | paddingTop: 10,
235 | paddingRight: 20
236 | }
237 | });
238 |
239 | export default createForm()(Register);
240 |
--------------------------------------------------------------------------------
/wyfReader/app/containers/Login.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import {
3 | StyleSheet,
4 | View,
5 | Text,
6 | Button,
7 | Image,
8 | TouchableOpacity,
9 | ActivityIndicator
10 | } from "react-native";
11 | import { Svg, Path, Circle } from "react-native-svg";
12 | import { connect } from "react-redux";
13 | import { List, InputItem } from "antd-mobile";
14 | import { createForm } from "rc-form";
15 | import { createAction, NavigationActions } from "../utils";
16 | import Util from "../utils/utils";
17 | import Request from "../libs/request";
18 |
19 | @connect((state) => ({loginData: state.app.loginData}))
20 | class Login extends Component {
21 | static navigationOptions = {
22 | title: "Login"
23 | };
24 | constructor(props) {
25 | super(props);
26 | this.state = {
27 | msg: "在这里登录!",
28 | paths: []
29 | };
30 | this.headStyle = [styles.header];
31 | }
32 | componentWillMount() {
33 |
34 | Request.get("/users/captcha").then(res => {
35 |
36 | if (res.code === 0) {
37 | let arr = res.response.replace(/>|<").split("|");
38 | let paths = arr.slice(1, arr.length - 1);
39 | let results = this.handlePaths(paths);
40 | this.setState({
41 | paths: results
42 | });
43 | }
44 | });
45 | }
46 | handlePaths = paths => {
47 | let results = paths.map((v, k) => {
48 | let path = {};
49 | let d = v.match(/(d="[^a-z]+")/);
50 | let fill = v.match(/(fill="[^\s]+")/);
51 | let stroke = v.match(/(stroke="[^\s]+")/);
52 |
53 | path.d = d ? d[0].split("=")[1].replace(/"/g, "") : "";
54 | path.fill = fill ? fill[0].split("=")[1].replace(/"/g, "") : "none";
55 | path.stroke = stroke ? stroke[0].split("=")[1].replace(/"/g, "") : "none";
56 | return ;
57 | });
58 | return results;
59 | };
60 | getCaptcha() {
61 | Request.get("/users/captcha").then(res => {
62 | if (res.code === 0) {
63 | let arr = res.response.replace(/>|<").split("|");
64 | let paths = arr.slice(1, arr.length - 1);
65 | let results = this.handlePaths(paths);
66 | this.setState({
67 | paths: results
68 | });
69 | }
70 | });
71 | }
72 | // onLogin = () => {
73 | // this.props.dispatch(createAction("app/login")());
74 | // };
75 |
76 | onClose = () => {
77 | this.props.dispatch(NavigationActions.back());
78 | };
79 | handleClick = () => {
80 | const { validateFields } = this.props.form;
81 | validateFields((err, value) => {
82 | if (err) {
83 | this.headStyle = [styles.header, styles.err];
84 | let msg
85 | if (err.code) {
86 | msg = "请输入验证码!"
87 | }
88 | if (err.username || err.password) {
89 | msg = "请输入账号和密码!"
90 | }
91 | alert(msg)
92 | this.getCaptcha()
93 | } else {
94 | const { username, password, code } = value;
95 | this.headStyle = [styles.header];
96 | this.props.dispatch({
97 | type: 'app/login',
98 | payload: {
99 | loginData: {
100 | password,
101 | username,
102 | code
103 | }
104 | }
105 | }).then(() => {
106 | const loginData = this.props.loginData;
107 | console.log(loginData,88)
108 | if (loginData.code === -1) {
109 | alert(loginData.msg)
110 | this.getCaptcha()
111 | }
112 | if (loginData.code === 11) {
113 | alert(loginData.msg)
114 | this.getCaptcha()
115 | }
116 |
117 | })
118 | }
119 |
120 | });
121 | };
122 | render() {
123 | const { getFieldProps } = this.props.form;
124 | return (
125 |
126 | {/* {fetching
127 | ?
128 | : }
129 | {!fetching && } */}
130 | (
133 | {this.state.msg}
134 | )}
135 | >
136 |
144 | 账号
145 |
146 |
155 | 密码
156 |
157 |
158 |
159 |
160 |
169 | 验证码
170 |
171 |
172 |
173 | {
176 | this.getCaptcha()
177 | }}
178 | >
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 | {
189 | this.onClose()
190 | }}>
191 | 返回
192 |
193 | {
194 | this.props.dispatch(NavigationActions.navigate({routeName: 'Register'}))
195 | }}>
196 | 没有账号?注册
197 |
198 |
199 |
200 | );
201 | }
202 | }
203 |
204 | const styles = StyleSheet.create({
205 | container: {
206 | flex: 1,
207 | justifyContent: "center"
208 | },
209 | header: {
210 | padding: 10,
211 | color: "#999"
212 | },
213 | err: {
214 | color: "#f00"
215 | },
216 | code: {
217 | flex: 0.6
218 | },
219 | codeWrap: {
220 | flex: 0.4,
221 | borderBottomWidth: Util.pixel,
222 | borderBottomColor: "#ddd",
223 | alignItems: "center",
224 | justifyContent: "center"
225 | },
226 | codeimg: {
227 | width: 80,
228 | height: 40
229 | },
230 | register: {
231 | color: '#007aff',
232 | textAlign: 'right',
233 | paddingTop: 10,
234 | paddingRight: 20
235 | }
236 | });
237 |
238 | export default createForm()(Login);
239 |
--------------------------------------------------------------------------------
/wyfReader/app/containers/Detail.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import {
3 | StyleSheet,
4 | View,
5 | Button,
6 | TouchableHighlight,
7 | TouchableOpacity,
8 | Image,
9 | Text
10 | } from "react-native";
11 | import { connect } from "react-redux";
12 | import DeviceInfo from "react-native-device-info";
13 |
14 | import { NavigationActions } from "../utils";
15 | import Util from "../utils/utils";
16 |
17 | class Detail extends Component {
18 | static navigationOptions = {
19 | title: "Detail",
20 | novelInfo: null
21 | };
22 | constructor(props) {
23 | super(props);
24 | this.state = {};
25 | }
26 | componentWillMount() {
27 | const { item } = this.props.navigation.state.params;
28 | const { title, url } = item;
29 | this.props
30 | .dispatch({
31 | type: "app/getNovelInfo",
32 | payload: {
33 | name: title,
34 | url
35 | }
36 | })
37 | .then(() => {
38 | this.setState({
39 | novelInfo: this.props.novelInfo,
40 | novelType: item.type
41 | });
42 | });
43 | }
44 | gotoDetail = () => {
45 | this.props.dispatch(NavigationActions.navigate({ routeName: "Detail" }));
46 | };
47 |
48 | goBack = () => {
49 | this.props.dispatch(NavigationActions.back());
50 | };
51 |
52 | orderNovel = (id, join) => {
53 | if (!join) {
54 | this.props.dispatch({
55 | type: 'app/orderNovel',
56 | payload: { id }
57 | })
58 | }
59 |
60 | this.props.dispatch(NavigationActions.navigate({ routeName: "Home" }));
61 | }
62 |
63 | renderBookInfo = (novelInfo) => {
64 | if (!novelInfo) {
65 | return (
66 |
67 | 等哈,正在拼命加载
68 |
69 | );
70 | }
71 | if (!novelInfo.name) {
72 | return (
73 |
74 | 哈哈,毛都没有一根!
75 |
76 | );
77 | }
78 | console.log(novelInfo,555555)
79 | return (
80 |
81 |
82 |
83 |
88 |
89 |
90 | {novelInfo.name}
91 | 类型:{this.state.novelType}
92 | 作者:{novelInfo.author}
93 | 更新时间:{novelInfo.updateTime}
94 |
95 | 最新章节:{novelInfo.lastChapterTitle}
96 |
97 |
98 |
99 |
100 | this.orderNovel(novelInfo._id, novelInfo.join)}
103 | >
104 |
105 | {novelInfo.join ? "已加入书架" : "加入书架"}
106 |
107 |
108 | this.props.dispatch(NavigationActions.navigate({ routeName: 'Reader', params: {id: novelInfo._id, name: novelInfo.name, num: 0} })) }
110 | >
111 | 开始阅读
112 |
113 |
114 |
115 | {novelInfo.introduction}
116 |
117 |
118 | );
119 | };
120 | render() {
121 | const { novelInfo } = this.state;
122 | console.log(novelInfo, 4545);
123 | return (
124 |
125 |
126 |
127 | {
129 | // this.props.navigateBack({ key: "Reader" });
130 | this.goBack();
131 | }}
132 | style={styles.button}
133 | >
134 |
138 |
139 |
140 |
141 | 作品详情
142 |
143 |
144 |
145 | {this.renderBookInfo(novelInfo)}
146 |
147 | );
148 | }
149 | }
150 |
151 | const styles = StyleSheet.create({
152 | scene: {
153 | flex: 1,
154 | justifyContent: "center",
155 | marginTop: 25
156 | },
157 | loading: {
158 | flex: 1,
159 | justifyContent: "center",
160 | alignItems: "center"
161 | },
162 | nav: {
163 | backgroundColor: "#A49B93",
164 | flexDirection: "row",
165 | alignItems: "center",
166 | zIndex: 99
167 | },
168 | button: {
169 | width: 44,
170 | height: 44,
171 | justifyContent: "center",
172 | alignItems: "center"
173 | },
174 | title: {
175 | flex: 1,
176 | height: 66,
177 | justifyContent: "center"
178 | },
179 | titleText: {
180 | marginTop: 25,
181 | fontSize: 18,
182 | textAlign: "center",
183 | color: "#ffffff"
184 | },
185 | leftButton: {
186 | marginTop: 25,
187 | marginRight: 5
188 | },
189 | detailSection: {
190 | flex: 1
191 | },
192 | novelInfo: {
193 | flex: 0.4,
194 | flexDirection: "row",
195 | backgroundColor: "#7C6958"
196 | },
197 | middle: {
198 | flex: 0.1,
199 | flexDirection: "row",
200 | borderBottomWidth: Util.pixel,
201 | borderColor: "#A5A5A5",
202 | paddingBottom: 20,
203 | paddingTop: 30
204 | },
205 | bigButton: {
206 | borderRadius: 5,
207 | flex: 0.5,
208 | height: 40,
209 | marginLeft: 20,
210 | marginRight: 20,
211 | backgroundColor: "#DD3F42"
212 | },
213 | bigText: {
214 | fontSize: 18,
215 | color: "#FEFBFB",
216 | marginTop: 10,
217 | textAlign: "center"
218 | },
219 | startBtn: {
220 | borderRadius: 5,
221 | flex: 0.5,
222 | height: 40,
223 | marginLeft: 20,
224 | marginRight: 20,
225 | backgroundColor: "#FF3F42"
226 | },
227 | startRead: {
228 | fontSize: 18,
229 | color: "#FEFBFB",
230 | marginTop: 10,
231 | textAlign: "center"
232 | },
233 | introduction: {
234 | flex: 0.4
235 | },
236 | infoLeft: {
237 | flex: 0.3
238 | },
239 | img: {
240 | marginTop: 20,
241 | marginLeft: 20,
242 | height: 110,
243 | width: 80
244 | // backgroundColor: '#A49B93',
245 | },
246 | infoRight: {
247 | flex: 0.6
248 | },
249 | novelTitle: {
250 | marginTop: 25,
251 | fontSize: 18,
252 | color: "#E5E3DF"
253 | },
254 | text: {
255 | marginTop: 15,
256 | color: "#E5E3DF"
257 | },
258 | introductionText: {
259 | marginTop: 10,
260 | marginLeft: 20,
261 | marginRight: 20
262 | }
263 | });
264 |
265 | const mapStateToProps = state => ({
266 | bookNames: state.app.bookNames,
267 | novelInfo: state.app.NovelInfo
268 | });
269 | export default connect(mapStateToProps)(Detail);
270 |
--------------------------------------------------------------------------------
/wyfReader/app/containers/Search.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { connect } from "react-redux";
3 | import {
4 | View,
5 | Text,
6 | TextInput,
7 | TouchableOpacity,
8 | Image,
9 | ScrollView,
10 | StyleSheet
11 | } from "react-native";
12 | import { Toast } from 'antd-mobile';
13 | import { NavigationActions } from "../utils";
14 | import Util from "../utils/utils";
15 |
16 | class Search extends Component {
17 | constructor(props) {
18 | super(props);
19 | this.state = {
20 | list: false,
21 | novelNames: [],
22 | searchBooks: []
23 | };
24 | }
25 | getNovelList = name => {
26 | Toast.loading('loading...');
27 | this.props
28 | .dispatch({
29 | type: "app/getNovelList",
30 | payload: { name }
31 | })
32 | .then(() => {
33 | Toast.hide();
34 | this.setState({
35 | searchBooks: this.props.searchBooks,
36 | list: true
37 | });
38 | });
39 | };
40 | goBack = () => {
41 | this.props.dispatch(NavigationActions.navigate({ routeName: "Home" }));
42 | };
43 | searchNovelList = ingredientsInput => {
44 | console.log(ingredientsInput);
45 | this.state.list = false;
46 | this.props
47 | .dispatch({
48 | type: "app/searchBookWords",
49 | payload: { text: ingredientsInput }
50 | })
51 | .then(() => {
52 | this.setState({
53 | novelNames: this.props.bookNames
54 | });
55 | });
56 | };
57 | goDetail = (item) => {
58 | this.props.dispatch(NavigationActions.navigate({ routeName: 'Detail', params: { item } }))
59 | }
60 | render() {
61 | return (
62 |
63 |
64 |
65 | {
68 | this.goBack();
69 | }}
70 | >
71 |
75 |
76 |
77 |
78 |
79 |
83 |
84 |
88 | this.searchNovelList(ingredientsInput)
89 | }
90 | />
91 |
92 |
93 |
94 |
95 |
96 |
97 | {this.state.list
98 | ? this.state.searchBooks.map((item, index) => (
99 | {
103 | this.goDetail(item);
104 | }}
105 | >
106 |
107 |
111 |
112 |
113 | {item.title}
114 |
115 | {item.introduction}
116 |
117 |
118 |
119 | {item.author}
120 |
121 | {item.type}
122 |
123 |
124 |
125 |
126 | ))
127 | : this.state.novelNames.map((item, index) => (
128 | {
132 | this.getNovelList(item.word);
133 | }}
134 | >
135 |
136 |
140 |
141 |
142 | {item.word}
143 |
144 |
145 | ))}
146 |
147 |
148 |
149 | );
150 | }
151 | }
152 |
153 | const styles = StyleSheet.create({
154 | container: {
155 | flex: 1
156 | },
157 | nav: {
158 | flex: 0.1,
159 | backgroundColor: "rgb(52,120,246)"
160 | },
161 | topBox: {
162 | height: 40,
163 | marginTop: 20,
164 | flexDirection: "row"
165 | },
166 | topBack: {
167 | flex: 0.1
168 | },
169 | backImg: {
170 | marginLeft: 5,
171 | marginTop: 3
172 | },
173 | topSearch: {
174 | flex: 0.9
175 | },
176 | searchSection: {
177 | flex: 1,
178 | marginRight: 20,
179 | borderRadius: 5,
180 | flexDirection: "row",
181 | backgroundColor: "#FFFFFF"
182 | },
183 | searchImgBox: {
184 | flex: 0.15
185 | },
186 | searchImg: {
187 | marginTop: 10,
188 | marginLeft: 20
189 | },
190 | searchInput: {
191 | flex: 0.85
192 | },
193 | content: {
194 | flex: 0.9
195 | },
196 | selectSection: {
197 | height: 50,
198 | borderBottomLeftRadius: 20,
199 | borderBottomWidth: Util.pixel,
200 | borderColor: "#999999",
201 | flexDirection: "row"
202 | },
203 | selectedLeft: {
204 | flex: 0.2
205 | },
206 | selectedImg: {
207 | marginTop: 15,
208 | marginLeft: 30
209 | },
210 | selectedRight: {
211 | flex: 0.6
212 | },
213 | novelName: {
214 | marginTop: 13,
215 | fontSize: 18,
216 | color: "#A9A9A9"
217 | },
218 | listSection: {
219 | height: 120,
220 | borderBottomLeftRadius: 20,
221 | borderBottomWidth: Util.pixel,
222 | borderColor: "#999999",
223 | flexDirection: "row"
224 | },
225 | listLeft: {
226 | flex: 0.3
227 | },
228 | listImg: {
229 | height: 100,
230 | width: 80,
231 | marginLeft: 8,
232 | marginTop: 10,
233 | marginBottom: 10
234 | },
235 | listRight: {
236 | flex: 0.7
237 | },
238 | title: {
239 | marginTop: 10,
240 | fontSize: 18
241 | },
242 | introduction: {
243 | marginTop: 10,
244 | marginRight: 10,
245 | color: "#A1A1A1"
246 | }
247 | });
248 |
249 | const mapStateToProps = state => ({
250 | bookNames: state.app.bookNames,
251 | bookList: state.app.bookList,
252 | searchBooks: state.app.searchBooks,
253 | });
254 | export default connect(mapStateToProps)(Search);
255 |
--------------------------------------------------------------------------------
/wyfReader/app/containers/Home.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import {
3 | PixelRatio,
4 | ScrollView,
5 | View,
6 | Image,
7 | Text,
8 | ListView,
9 | TouchableOpacity,
10 | StyleSheet,
11 | RefreshControl,
12 | TouchableWithoutFeedback
13 | } from 'react-native'
14 | import { connect } from 'react-redux'
15 | import Swipeout from 'react-native-swipeout'
16 | import { NavigationActions, createAction } from '../utils'
17 |
18 |
19 | @connect(({ app }) => ({ app }))
20 | class Home extends PureComponent {
21 | static navigationOptions = {
22 | title: 'wyfReader',
23 | tabBarLabel: 'Book',
24 | tabBarIcon: ({ focused, tintColor }) =>
25 | ,
29 | }
30 | constructor(props) {
31 | super(props)
32 | this.state = {
33 | isRefreshing: false,
34 | loaded: 0,
35 | dataSource: new ListView.DataSource({
36 | rowHasChanged: (row1, row2) => row1 !== row2
37 | })
38 | }
39 | // this.closeSwipeout = this.closeSwipeout.bind(this)
40 | this._onRefresh = this._onRefresh.bind(this)
41 | this.renderRow = this.renderRow.bind(this)
42 | }
43 | componentWillMount() {
44 | this.props.dispatch(createAction('app/getBookList')())
45 | }
46 | closeSwipeout = () => {
47 |
48 | }
49 | gotoDetail = () => {
50 | this.props.dispatch(NavigationActions.navigate({ routeName: 'Search' }))
51 | }
52 | delectNovel = (id) => {
53 | this.props.dispatch({
54 | type: 'app/deleteNovel',
55 | payload: {
56 | id
57 | }
58 | })
59 | }
60 | _onRefresh= () => {
61 | this.setState({isRefreshing: true})
62 | this.props.dispatch({ type: 'app/getBookList', payload: {} }).then(() => {
63 | this.setState({
64 | loaded: this.state.loaded + 10,
65 | isRefreshing: false,
66 | dataSource: new ListView.DataSource({
67 | rowHasChanged: (row1, row2) => row1 !== row2
68 | })
69 | })
70 | })
71 |
72 | }
73 | renderRow(list, index, length) {
74 | const that = this
75 | const swipeoutBtns = [
76 | {
77 | text: '删除',
78 | onPress: () => {that.delectNovel(list._id)}
79 | }
80 | ]
81 | let conStyle = [styles.item, styles.row]
82 | if (index * 1 === length - 1) {
83 | conStyle = [styles.item, styles.row, styles.rmLine]
84 | }
85 | return (
86 |
92 | this.props.dispatch(NavigationActions.navigate({ routeName: 'Reader', params: {id: list.novel._id, name: list.novel.name, num: list.number} })) }
94 | style={conStyle}>
95 |
96 | {/* */}
97 |
98 |
99 |
100 | {list.novel.name}
101 | 最新: {list.novel.lastChapterTitle}
102 |
103 |
104 | {list.novel.updateTime}
105 |
106 |
107 | {/*
108 | ssssss
109 | */}
110 |
111 |
112 | )
113 | }
114 | renderBookList = (lists) => (
115 |
127 | }>
128 |
131 | this.renderRow(rowData, rowId, lists.length)} />
135 |
136 |
137 | )
138 |
139 | render() {
140 | const { bookList } = this.props.app
141 | // const bookList = [{
142 | // _id: 1111,
143 | // novel: {
144 | // _id: 1234,
145 | // img: require('../images/test.jpg'),
146 | // name: '永夜君王',
147 | // lastChapterTitle: 'hahah',
148 | // updateTime: '2017-12-12'
149 | // }
150 | // }]
151 | if (!bookList) return 加载中。。。
152 | return (
153 |
154 |
155 |
156 |
157 |
158 |
159 | wyfReader
160 |
161 |
162 | this.gotoDetail() }>
165 |
166 |
167 |
168 |
169 | {bookList.length === 0?没有数据:this.renderBookList(bookList)}
170 |
171 | )
172 | }
173 | }
174 |
175 | const styles = StyleSheet.create({
176 | scene: {
177 | flex: 1,
178 | },
179 | noDataTxt: {
180 | textAlign: 'center',
181 | marginTop: 23
182 | },
183 | scrollSection: {
184 | flex: 1,
185 | marginTop: 0
186 | },
187 | rightButton: {
188 | marginTop: 25,
189 | marginRight: 5,
190 | },
191 | button: {
192 | width: 44,
193 | height: 44,
194 | justifyContent: 'center',
195 | alignItems: 'center',
196 | },
197 | nav: {
198 | backgroundColor: 'rgb(52,120,246)',
199 | flexDirection: 'row',
200 | alignItems: 'center',
201 | zIndex: 99
202 | },
203 | title: {
204 | flex: 1,
205 | height: 66,
206 | justifyContent: 'center',
207 | },
208 | titleText: {
209 | marginTop: 25,
210 | fontSize: 18,
211 | textAlign: 'center',
212 | color: '#ffffff',
213 | },
214 | btnText: {
215 | fontSize: 16,
216 | marginRight: 10,
217 | color: '#ffffff',
218 | },
219 | item: {
220 | height: 92,
221 | borderBottomLeftRadius: 20,
222 | borderBottomRightRadius: 20,
223 | borderBottomWidth: 2 / PixelRatio.get(),
224 | borderColor: '#ddd',
225 | },
226 | rmLine: {
227 | borderColor: 'transparent',
228 | },
229 | row:{
230 | flexDirection: 'row',
231 | },
232 | left: {
233 | marginTop: 10
234 | },
235 | img:{
236 | height: 70,
237 | width:90,
238 | },
239 | text:{
240 | color: '#A5A5A5',
241 | marginTop: 6,
242 | fontSize: 12,
243 | },
244 | clock: {
245 | marginTop: 8,
246 | },
247 | duration: {
248 | marginLeft: 4,
249 | color: '#A5A5A5',
250 | marginTop: 9 ,
251 | fontSize: 12,
252 | },
253 | novelTitle:{
254 | fontSize:16,
255 | marginTop: 10,
256 | },
257 | })
258 |
259 | export default Home
260 |
--------------------------------------------------------------------------------
/wu-server/src/modules/chapters.js/controller.js:
--------------------------------------------------------------------------------
1 | import Bookshelf from '../../models/bookshelfs'
2 | import Chapter from '../../models/chapters'
3 | import Novel from '../../models/novels'
4 | import * as Crawler from '../../utils/crawler'
5 |
6 |
7 | /**
8 | @api {POST} /chapters 获取章节信息
9 | @apiVersion 1.0.0
10 | @apiName 获取章节信息
11 | @apiGroup Chapters
12 |
13 | @apiExample Example usage:
14 | curl -H "Content-Type: application/json" -X GET http://localhost:5000/chapters/58c4cbce9e4dad30f80d34e7
15 |
16 | @apiParam {String} novelId 小说ID.
17 | @apiParam {String} num 小说章节.
18 |
19 | @apiSuccessExample {json} Success-Response:
20 | HTTP/1.1 200 OK
21 | {
22 | "detail": {
23 | "_id": "58c4cbce9e4dad30f80d373e",
24 | "title": "第五百九十九章,崩盘(2)",
25 | "postfix": "3455564.html",
26 | "number": 598,
27 | "novel": {
28 | "_id": "58c4cbce9e4dad30f80d34e7",
29 | "url": "http://www.37zw.com/3/3731/",
30 | "name": "1855美国大亨"
31 | },
32 | "__v": 0,
33 | "content": " 安克雷奇港突然的就安静了下来,自打几个月前危机爆发后,尤其是标准石油宣布将下调燃油和原油价格之后,抵达这里的船只就越来越少了,最近一个星期,平均每天都只有几条船抵达港口。 而这些船当中,真正的最有意义的超级油轮却没多少,这个星期里仅仅只来了一条而已。其他的船都是些渔船呀,捕鲸船呀什么的。据说德国人现在还在购入石油,但是因为从标准直接购入石油的成本已经比从阿拉斯加开采更低了,所以那些油轮也不再往这边跑了。  十五的地步。剩下的那些没有失业的工人,他们的工资也大幅下降了两成到五成。虽然信奉工联主义的社民党努力的安抚,但是自发的罢工,乃至暴.动也开始此起彼伏的发生。 [三七中文手机版 m.37zw.com]"
34 | }
35 | }
36 |
37 | @apiErrorExample {json} Error-Response:
38 | HTTP/1.1 422 Unprocessable Entity
39 | {
40 | "status": 422,
41 | "error": ""
42 | }
43 | */
44 | export async function getChapterInfo (ctx) {
45 | let {id, num} = ctx.request.body
46 | let detail, chapter
47 | const user = ctx.state.user
48 |
49 | try {
50 | chapter = await Chapter.findOne({novel: id})
51 | detail = chapter.chapters[num]
52 | } catch (e) {
53 | Handle.sendEmail(e.message)
54 | ctx.throw(422, e.message)
55 | }
56 |
57 | // 如果没有内容,会去网站爬取
58 | if (detail.content) {
59 | ctx.body = {
60 | success: true
61 | }
62 | } else {
63 | const url = `${detail.postfix}`
64 | try {
65 | const content = await Crawler.getChapterContent(url)
66 | chapter.chapters[num].content = content
67 | await Chapter.update({novel: id}, { chapters: chapter.chapters })
68 | } catch (e) {
69 | Handle.sendEmail(e.message)
70 | ctx.throw(422, e.message)
71 | }
72 | }
73 | const response = detail
74 | await Bookshelf.update({ user: user._id, novel: id }, { number: num })
75 | ctx.body = {
76 | response
77 | }
78 | }
79 |
80 | /**
81 | @api {GET} /chapters/firstRender/:id 获取首次渲染章节
82 | @apiPermission User
83 | @apiVersion 1.0.0
84 | @apiName 获取首次渲染章节
85 | @apiGroup Chapters
86 |
87 | @apiExample Example usage:
88 | curl -H "Content-Type: application/json" "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU4YjhmZDRkODUyYTE1YzliNmYyNjI3MSIsImlhdCI6MTQ4ODU1MTc2N30.IEgYwmgyqOBft9s38ool7cmuC2yIlWYVLf4WQzcbqAI" -X GET http://localhost:5000/chapters/firstRender/58c4cbce9e4dad30f80d34e7
89 |
90 | @apiSuccess {Object} progress 章节进度
91 |
92 | @apiSuccessExample {json} Success-Response:
93 | HTTP/1.1 200 OK
94 | {
95 | "response": {
96 | "chapters": [
97 | {
98 | "_id": "58ccc05923ff5c6b9d5053cc",
99 | "title": "上架感言",
100 | "postfix": "2352954.html",
101 | "number": 0,
102 | "novel": "58ccc05923ff5c6b9d5053cb",
103 | "__v": 0,
104 | "content": " 《1852铁血中华》已经上架。 这次写太平天国时代有很多原因,不过最重要的原因之一,是因为太平天国运动是清末民初最后一次体制外的造反,也是中国到现在为止的最后一次大规模农民起义。 一提起太平天国来,现在的人很容易想起的就是拜上帝教、洪教主,而实际情况又是如何? 在那个时代,满清朝廷虽然给洋人跪了,但是整个中国各阶层却远没有20世纪初面临亡国灭种危局的绝望之情。 这就是太平天国时代的中国。 在全世界主要大国正向着空前激烈的时代突飞猛进的时候,中国的变化远没有世界来的激烈。本书的主角要承担的责任,就是在中国掀起比世界更加激烈的变化。 对大家以往的支持,绯红万分感谢!绯红一定会努力继续写书,请大家继续支持,继续订阅!谢谢!! [三七中文 www.37zw.com]百度搜索“37zw.com”"
105 | },
106 | {
107 | "_id": "58ccc05923ff5c6b9d5053cd",
108 | "title": "第1章 韦泽(一)",
109 | "postfix": "2352958.html",
110 | "number": 1,
111 | "novel": "58ccc05923ff5c6b9d5053cb",
112 | "__v": 0,
113 | "content": " 1852年2月6日上午,广西大瑶山一处山岭上的树林旁边。 二十几名年轻的战士正在战场上,他们都穿着广西普通百姓的服色,身上是黑色的粗布短衣短裤,腰间束了白色的粗布腰带,腿上打着白布绑腿,脚上则是草鞋。因为天冷,战士们脚上缠了原本可能是白色,现在已经脏兮兮看不出颜色的裹脚布。众人脑袋上并不是广西那种包头布,因为大家都把长发在头上扎了一个发髻,所以在脑袋上箍了一条白色粗布发带,猛看上仿佛是一支奔丧的队伍。 一位浓眉大眼的青年停在战场中央,棱角分明脸庞有着少年轻人特有的圆润感觉,怎么看都不超过20岁的模样。这名笑道:“辫子又不重,带回去正好请功!” 广西号称百万大山,大瑶山山峦叠翠,在战场附近就有山谷。清军的尸体被抛入山谷,转眼就没了踪影。清军还能留在战场上的是他们的武器,十几名清军的武器中一半是长枪,另一半则是火绳枪。这就是韦泽回到这个时代之后另一件不能立刻接受的事情。这个时业化的日本进行着艰苦卓绝的战斗,并且顽强的不断扩大敌后根据地,把中国的国土从侵略者手中一寸寸的夺回来。 满清的火绳枪固然与这时代流行的燧发枪有不小差距,却远没有到达一场战斗只有五发子弹的八路军与敌人之间的差距。在武器装备差距有限的局面下还能被打得签署了无数丧权辱国的条约,这样的满清是必须消灭掉的。不消灭掉满清,就注定没有中国的未来。韦泽对此坚信不移。 十几名清军携带的钱财不多,韦泽登记造册后让负责后勤的伍长林阿生把财物收起来。 “出发!”韦泽命道。26人的小队扛起自己的装备,在韦泽的带领下向着东北方向继续前进了。 [三七中文 www.37zw.com]百度搜索“37zw.com”"
114 | }
115 | ],
116 | "progress": 0
117 | }
118 | }
119 |
120 | @apiErrorExample {json} Error-Response:
121 | HTTP/1.1 422 Unprocessable Entity
122 | {
123 | "status": 422,
124 | "error": ""
125 | }
126 | */
127 | export async function getFirstRenderChapter (ctx) {
128 | let bookshelf, chapter, nextChapter, chapters, currentChapter
129 | const id = ctx.query.id
130 | const num = ctx.query.num * 1
131 | const user = ctx.state.user
132 | await Bookshelf.update({user: user._id}, { number: num })
133 | try {
134 | bookshelf = await Bookshelf.findOne({user: user._id, novel: id}).populate('novel')
135 | chapter = await Chapter.findById(bookshelf.chapter)
136 | nextChapter = chapter.chapters[num + 1]
137 | if (bookshelf.novel.countChapter != num + 1 && !nextChapter.content) {
138 | nextChapter = chapter.chapters[num + 1]
139 | if (!nextChapter.content) {
140 | const url = `${nextChapter.postfix}`
141 |
142 | const content = await Crawler.getChapterContent(url)
143 | nextChapter.content = content
144 | chapter.chapters[num + 1].content = content
145 | await Chapter.update({_id: bookshelf.chapter}, { chapters: chapter.chapters })
146 | // await chapter.save()
147 | }
148 | }
149 | } catch (e) {
150 | console.log(e)
151 | Handle.sendEmail(e.message)
152 | ctx.throw(423, e.message)
153 | }
154 | currentChapter = chapter.chapters[num]
155 | if (!currentChapter.content) {
156 | const url = `${currentChapter.postfix}`
157 | try {
158 | const content = await Crawler.getChapterContent(url)
159 | currentChapter.content = content
160 | chapter.chapters[num].content = content
161 | await Chapter.update({_id: bookshelf.chapter}, { chapters: chapter.chapters })
162 | } catch (e) {
163 | Handle.sendEmail(e.message)
164 | ctx.throw(424, e.message)
165 | }
166 | }
167 |
168 | if (nextChapter) {
169 | chapters = [currentChapter, nextChapter]
170 | } else {
171 | chapters = [currentChapter]
172 | }
173 | const response = {
174 | chapters: chapters,
175 | progress: bookshelf.progress,
176 | countChapter: bookshelf.novel.countChapter
177 | }
178 |
179 | ctx.body = {
180 | response
181 | }
182 | }
183 |
184 | /**
185 | @api {GET} /chapter/next/:id 获取下一章信息
186 | @apiVersion 1.0.0
187 | @apiName 获取下一章信息
188 | @apiGroup Chapters
189 |
190 | @apiExample Example usage:
191 | curl -H "Content-Type: application/json" -X GET http://localhost:5000/chapters/next/58bc1ec43f9cdc31b9bea8dc
192 |
193 |
194 | @apiSuccessExample {json} Success-Response:
195 | HTTP/1.1 200 OK
196 | {
197 | "detail": {
198 | "_id": "58c4cbce9e4dad30f80d373e",
199 | "title": "第五百九十九章,崩盘(2)",
200 | "postfix": "3455564.html",
201 | "number": 598,
202 | "novel": {
203 | "_id": "58c4cbce9e4dad30f80d34e7",
204 | "url": "http://www.37zw.com/3/3731/",
205 | "name": "1855美国大亨"
206 | },
207 | "__v": 0,
208 | "content": " 安克雷奇港突然的就安静了下来,自打几个月前危机爆发后,尤其是标准石油宣布将下调燃油和原油价格之后,抵达这里的船只就越来越少了,最近一个星期,平均每天都只有几条船抵达港口。 而这些船当中,真正的最有意义的超级油轮却没多少,这个星期里仅仅只来了一条而已。其他的船都是些渔船呀,捕鲸船呀什么的。据说德国人现在还在购入石油,但是因为从标准直接购入石油的成本已经比从阿拉斯加开采更低了,所以那些油轮也不再往这边跑了。  十五的地步。剩下的那些没有失业的工人,他们的工资也大幅下降了两成到五成。虽然信奉工联主义的社民党努力的安抚,但是自发的罢工,乃至暴.动也开始此起彼伏的发生。 [三七中文手机版 m.37zw.com]"
209 | }
210 | }
211 |
212 | @apiErrorExample {json} Error-Response:
213 | HTTP/1.1 422 Unprocessable Entity
214 | {
215 | "status": 422,
216 | "error": ""
217 | }
218 | */
219 | export async function getNextChapterInfo (ctx) {
220 | const id = ctx.params.id
221 | try {
222 | var currentDetail = await Chapter.getContent(id)
223 | } catch (e) {
224 | Handle.sendEmail(e.message)
225 | ctx.throw(422, e.message)
226 | }
227 |
228 | const chapterNum = currentDetail.number + 1
229 | const novelId = currentDetail.novel.id
230 | try {
231 | var nextDetail = await Chapter.findByNumber(novelId, chapterNum)
232 | } catch (e) {
233 | Handle.sendEmail(e.message)
234 | ctx.throw(422, e.message)
235 | }
236 |
237 | if (nextDetail.content) {
238 | ctx.body = {
239 | success: true
240 | }
241 | } else {
242 | const url = `${currentDetail.novel.url}${nextDetail.postfix}`
243 | try {
244 | const content = await Crawler.getChapterContent(url)
245 | nextDetail.content = content
246 | await nextDetail.save()
247 | } catch (err) {
248 | Handle.sendEmail(e.message)
249 | ctx.throw(422, e.message)
250 | }
251 | }
252 |
253 | const response = nextDetail.toJSON()
254 |
255 | ctx.body = {
256 | detail: response
257 | }
258 | }
259 |
260 | /**
261 | @api {GET} /chapter/last/:id 获取上一章信息
262 | @apiVersion 1.0.0
263 | @apiName 获取上一章信息
264 | @apiGroup Chapters
265 |
266 | @apiExample Example usage:
267 | curl -H "Content-Type: application/json" -X GET http://localhost:5000/chapters/last/58bc1ec43f9cdc31b9bea8dc
268 |
269 | @apiSuccessExample {json} Success-Response:
270 | HTTP/1.1 200 OK
271 | {
272 | "detail": {
273 | "_id": "58c4cbce9e4dad30f80d373e",
274 | "title": "第五百九十九章,崩盘(2)",
275 | "postfix": "3455564.html",
276 | "number": 598,
277 | "novel": {
278 | "_id": "58c4cbce9e4dad30f80d34e7",
279 | "url": "http://www.37zw.com/3/3731/",
280 | "name": "1855美国大亨"
281 | },
282 | "__v": 0,
283 | "content": " 安克雷奇港突然的就安静了下来,自打几个月前危机爆发后,尤其是标准石油宣布将下调燃油和原油价格之后,抵达这里的船只就越来越少了,最近一个星期,平均每天都只有几条船抵达港口。 而这些船当中,真正的最有意义的超级油轮却没多少,这个星期里仅仅只来了一条而已。其他的船都是些渔船呀,捕鲸船呀什么的。据说德国人现在还在购入石油,但是因为从标准直接购入石油的成本已经比从阿拉斯加开采更低了,所以那些油轮也不再往这边跑了。  十五的地步。剩下的那些没有失业的工人,他们的工资也大幅下降了两成到五成。虽然信奉工联主义的社民党努力的安抚,但是自发的罢工,乃至暴.动也开始此起彼伏的发生。 [三七中文手机版 m.37zw.com]"
284 | }
285 | }
286 |
287 | @apiErrorExample {json} Error-Response:
288 | HTTP/1.1 422 Unprocessable Entity
289 | {
290 | "status": 422,
291 | "error": ""
292 | }
293 | */
294 | export async function getLastChapterInfo (ctx) {
295 | const id = ctx.params.id
296 | try {
297 | var currentDetail = await Chapter.getContent(id)
298 | } catch (e) {
299 | Handle.sendEmail(e.message)
300 | ctx.throw(422, err.message)
301 | }
302 |
303 | const chapterNum = currentDetail.number - 1
304 | const novelId = currentDetail.novel.id
305 | try {
306 | var lastDetail = await Chapter.findByNumber(novelId, chapterNum)
307 | } catch (e) {
308 | Handle.sendEmail(e.message)
309 | ctx.throw(422, e.message)
310 | }
311 |
312 | if (lastDetail.content) {
313 | ctx.body = {
314 | success: true
315 | }
316 | } else {
317 | const url = `${currentDetail.novel.url}${lastDetail.postfix}`
318 | try {
319 | const content = await Crawler.getChapterContent(url)
320 | lastDetail.content = content
321 | await lastDetail.save()
322 | } catch (e) {
323 | Handle.sendEmail(e.message)
324 | ctx.throw(422, e.message)
325 | }
326 | }
327 |
328 | const response = lastDetail.toJSON()
329 |
330 | ctx.body = {
331 | detail: response
332 | }
333 | }
334 |
--------------------------------------------------------------------------------
/wu-server/src/modules/novels.js/controller.js:
--------------------------------------------------------------------------------
1 | import Novel from '../../models/novels'
2 | import Bookshelf from '../../models/bookshelfs'
3 | import Chapter from '../../models/chapters'
4 |
5 | import * as Crawler from '../../utils/crawler'
6 | import simplePinyin from 'simple-pinyin'
7 | import cheerio from 'cheerio'
8 | import config from '../../../config'
9 |
10 | /**
11 | @api {GET} /novels/directory/:id 获取小说目录
12 | @apiDescription order是目录顺序1(升序)-1(降序)
13 | @apiVersion 1.0.0
14 | @apiName 获取小说目录
15 | @apiGroup Novels
16 |
17 | @apiExample Example usage:
18 | curl -H "Content-Type: application/json" -X GET http://localhost:5000/novels/directory/58c4cbce9e4dad30f80d34e7?order=1
19 |
20 | @apiSuccessExample {json} Success-Response:
21 | HTTP/1.1 200 OK
22 | {
23 | "directory": [
24 | {
25 | "_id": "58c4cbce9e4dad30f80d34e8",
26 | "title": "第一章,缺钱的超级大腿",
27 | "number": 0
28 | },
29 | {
30 | "_id": "58c4cbce9e4dad30f80d34e9",
31 | "title": "第二章,辍学的念头",
32 | "number": 1
33 | }
34 | ]
35 | }
36 | @apiError UnprocessableEntity
37 |
38 | @apiErrorExample {json} Error-Response:
39 | HTTP/1.1 422 Unprocessable Entity
40 | {
41 | "status": 422,
42 | "error": ""
43 | }
44 | */
45 | export async function downloadChapters (ctx) {
46 | let results
47 | const id = ctx.params.id
48 | const options = {
49 | attributes: ['title', 'content', 'number'],
50 | order: 1
51 | }
52 | try {
53 | results = await Chapter.getDirectory(id, options)
54 | } catch (err) {
55 | Handle.sendEmail(e.message)
56 | ctx.throw(422, err.message)
57 | }
58 |
59 | ctx.body = {
60 | results
61 | }
62 | }
63 |
64 | /**
65 | @api {GET} /novels/search/zh 搜索小说名称
66 | @apiDescription
67 | @apiVersion 1.0.0
68 | @apiName 搜索小说名称
69 | @apiGroup Novels
70 | @apiExample Example usage:
71 | curl -H "Content-Type: application/json" -X GET http://localhost:5000/novels/search/zh?keyword=永夜
72 |
73 | @apiParam {String} keyword 搜索关键词.
74 |
75 | @apiSuccessExample {json} Success-Response:
76 | 纵横
77 | HTTP/1.1 200 OK
78 | {
79 | "response": {
80 | "q": "yongye",
81 | "r": [
82 | {
83 | "word": "永夜君王"
84 | },
85 | {
86 | "word": "永夜之帝国双璧"
87 | },
88 | {
89 | "word": "永夜帝王"
90 | },
91 | {
92 | "word": "永夜王座"
93 | }
94 | ]
95 | }
96 | }
97 | 起点
98 | {
99 | "response": {
100 | "code": "0",
101 | "query": "Unit",
102 | "suggestions": [
103 | {
104 | "value": "永夜君王",
105 | data:{ category }
106 | },
107 | ]
108 | }
109 | }
110 | @apiErrorExample {json} Error-Response:
111 | HTTP/1.1 422 Unprocessable Entity
112 | {
113 | "status": 422,
114 | "error": ""
115 | }
116 | */
117 | export async function searchFromZH (ctx) {
118 | const keyword = ctx.query.keyword
119 | //将汉字转拼音
120 | const words = simplePinyin(keyword, { pinyinOnly: false })
121 | let word = ''
122 | words.forEach(function (item) {
123 | word += item
124 | })
125 |
126 | // 调用纵横api接口
127 | const url = `http://search.zongheng.com/search/mvc/suggest.do?keyword=${word}`
128 | // 起点api
129 | const qdUrl = `https://www.qidian.com/ajax/Search/AutoComplete?_csrfToken=&siteid=1&query=${word}`
130 | try {
131 | var body = await Crawler.getBody(qdUrl)
132 | } catch (e) {
133 | Handle.sendEmail(e.message)
134 | ctx.throw(422, err.message)
135 | }
136 | const response = JSON.parse(body)
137 | let r = []
138 | response.suggestions.map(v => {
139 | r.push({ word: v.value })
140 | })
141 | ctx.body = {
142 | response: r,
143 | q: word
144 | }
145 | }
146 |
147 | /**
148 | @api {GET} novels/search/bqk 获取小说列表
149 | @apiDescription
150 | @apiVersion 1.0.0
151 | @apiName 获取小说列表
152 | @apiGroup Novels
153 | @apiExample Example usage:
154 | curl -H "Content-Type: application/json" -X GET http://localhost:5000/novels/search/bqk?name=永夜
155 |
156 | @apiParam {String} keyword 搜索关键词.
157 |
158 | @apiSuccessExample {json} Success-Response:
159 | HTTP/1.1 200 OK
160 | {
161 | "response": [
162 | {
163 | "title": "永夜君王",
164 | "img": "http://www.37zw.com/d/image/2/2790/2790s.jpg",
165 | "url": "http://www.37zw.com/2/2790/",
166 | "introduction": "千夜自困苦中崛起,在背叛中坠落。自此一个人,一把枪,行在永夜与黎明之间,却走出一段传奇。若永夜注定是他的命运,那他也要成为主宰的王。",
167 | "author": "烟雨江南",
168 | "type": "玄幻小说"
169 | },
170 | {
171 | "title": "永镇天渊",
172 | "img": "http://www.37zw.com/d/image/5/5926/5926s.jpg",
173 | "url": "http://www.37zw.com/5/5926/",
174 | "introduction": "从天坠落,当永耀烈阳归于寂灭……当诸天银河黯淡无光,当万事万物化为墟烬……这是被天渊笼罩的世界这是注定沦于破灭的宇宙然而高川降临了所以未来的一切便都被",
175 | "author": "阴天神隐",
176 | "type": "修真小说"
177 | }
178 | ]
179 | }
180 | @apiErrorExample {json} Error-Response:
181 | HTTP/1.1 422 Unprocessable Entity
182 | {
183 | "status": 422,
184 | "error": ""
185 | }
186 | */
187 | export async function searchFromBQK (ctx) {
188 | const name = ctx.query.name
189 |
190 | // 书趣阁搜索网站
191 | const url = `http://zhannei.baidu.com/cse/search?s=920895234054625192&q=${name}`
192 | try {
193 | var body = await Crawler.request(encodeURI(url))
194 | } catch (e) {
195 | Handle.sendEmail(e.message)
196 | ctx.throw(422, e.message)
197 | }
198 | const $ = cheerio.load(body, {decodeEntities: false})
199 |
200 | const length = $('.result-game-item-detail').length
201 |
202 | //爬取小说信息,用于展示
203 | let arr = []
204 | for (let i = 0; i < length; i++) {
205 | const title = $('.result-game-item-title a')[i].attribs.title
206 | const img = $('.result-game-item-pic img')[i].attribs.src
207 | const url = $('.result-game-item-title a')[i].attribs.href
208 | const introduction = $('.result-game-item-desc').eq(i).text().replace(/\s/g, "")
209 | const author = $('.result-game-item-info').eq(i).find('p:first-child span:last-child').text().replace(/\s/g, "")
210 | const type = $('.result-game-item-info').eq(i).find('p:nth-child(2) span:last-child').text().replace(/\s/g, "")
211 | let json = {
212 | title: title,
213 | img: img,
214 | url: url,
215 | introduction: introduction,
216 | author: author,
217 | type: type
218 | }
219 | if (url.indexOf('.php') == -1) {
220 |
221 | arr.push(json)
222 | }
223 | }
224 |
225 | ctx.body = {
226 | response: arr
227 | }
228 | }
229 |
230 | /**
231 | @api {POST} /novels/acquire 爬取小说
232 | @apiPermission User
233 | @apiVersion 1.0.0
234 | @apiName 爬取小说
235 | @apiGroup Novels
236 |
237 | @apiExample Example usage:
238 | curl -H "Content-Type: application/json" "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU4YjhmZDRkODUyYTE1YzliNmYyNjI3MSIsImlhdCI6MTQ4ODU1MTc2N30.IEgYwmgyqOBft9s38ool7cmuC2yIlWYVLf4WQzcbqAI" -X GET http://localhost:5000/novels/acquire
239 |
240 | @apiParam {Object} novel 小说对象 (必需)
241 | @apiParam {String} novel.name 小说名称.
242 | @apiParam {String} novel.url 爬取网站的url.
243 |
244 | @apiSuccessExample {json} Success-Response:
245 | HTTP/1.1 200 OK
246 | {
247 | "success": true
248 | }
249 | @apiError UnprocessableEntity
250 |
251 | @apiErrorExample {json} Error-Response:
252 | HTTP/1.1 422 Unprocessable Entity
253 | {
254 | "status": 422,
255 | "error": ""
256 | }
257 | */
258 | export async function getNovel (ctx) {
259 | const user = ctx.state.user
260 | const { name, url } = ctx.request.body.novel
261 | try {
262 | var novel = await Novel.findOne({name: name})
263 | } catch (e) {
264 | ctx.throw(422, e.message)
265 | }
266 |
267 | // 判断数据库中是否有该小说,没有在去网站爬取
268 | if (novel) {
269 | try {
270 | var bookshelf = await Bookshelf.findOne({novel: novel.id, user: user.id})
271 | } catch (e) {
272 | Handle.sendEmail(e.message)
273 | ctx.throw(422, e.message)
274 | }
275 | const response = novel.toJSON()
276 | if (bookshelf) {
277 | response.join = true
278 | } else {
279 | response.join = false
280 | }
281 |
282 | ctx.body = {
283 | novelInfo: response
284 | }
285 | } else {
286 | try {
287 | var $ = await Crawler.getHtml(url)
288 | } catch (e) {
289 | Handle.sendEmail(e.message)
290 | ctx.throw(422, e.message)
291 | }
292 | let novelInfo = {}
293 | const author = $('#info p').eq(0).text()
294 | if (!author) {
295 | ctx.body = {
296 | code: 404,
297 | msg: '无小说信息'
298 | }
299 | return
300 | }
301 | const updateTime = $('#info p').eq(2).text()
302 | const img = $('#fmimg img')[0].attribs.src
303 | novelInfo.url = url
304 | novelInfo.name = $('#info h1').text()
305 | novelInfo.author = author.split(':')[1].trim()
306 | novelInfo.img = config.crawlerUrl + img
307 | novelInfo.updateTime = updateTime.substring(5, updateTime.length)
308 | novelInfo.introduction = $('#intro').text()
309 | novel = new Novel(novelInfo)
310 | try {
311 | await novel.save()
312 | } catch (e) {
313 | Handle.sendEmail(e.message)
314 | ctx.throw(423, e.message)
315 | }
316 | const novelId = novel.id
317 | await Crawler.getNovel($, novelId, url)
318 | try {
319 | var titles = await Chapter.getTitles(novelId)
320 | var chapters = titles[0].chapters
321 | var count = chapters.length
322 | } catch (e) {
323 | Handle.sendEmail(e.message)
324 | ctx.throw(424, e.message)
325 | }
326 | novel.lastChapterTitle = chapters[chapters.length - 1].title
327 | novel.countChapter = count
328 | try {
329 | await novel.save()
330 | } catch (e) {
331 | Handle.sendEmail(e.message)
332 | ctx.throw(425, e.message)
333 | }
334 | const response = novel.toJSON()
335 | response.join = false
336 | ctx.body = {
337 | code: 0,
338 | novelInfo: response
339 | }
340 | }
341 | }
342 |
343 | /**
344 | @api {GET} /novels/directory/:id 获取小说目录
345 | @apiDescription order是目录顺序1(升序)-1(降序)
346 | @apiVersion 1.0.0
347 | @apiName 获取小说目录
348 | @apiGroup Novels
349 |
350 | @apiExample Example usage:
351 | curl -H "Content-Type: application/json" -X GET http://localhost:5000/novels/directory/58c4cbce9e4dad30f80d34e7?order=1
352 |
353 | @apiSuccessExample {json} Success-Response:
354 | HTTP/1.1 200 OK
355 | {
356 | "directory": [
357 | {
358 | "_id": "58c4cbce9e4dad30f80d34e8",
359 | "title": "第一章,缺钱的超级大腿",
360 | "number": 0
361 | },
362 | {
363 | "_id": "58c4cbce9e4dad30f80d34e9",
364 | "title": "第二章,辍学的念头",
365 | "number": 1
366 | }
367 | ]
368 | }
369 | @apiError UnprocessableEntity
370 |
371 | @apiErrorExample {json} Error-Response:
372 | HTTP/1.1 422 Unprocessable Entity
373 | {
374 | "status": 422,
375 | "error": ""
376 | }
377 | */
378 | export async function getDirectory (ctx) {
379 | let results
380 | const id = ctx.params.id
381 | const order = ctx.query.order
382 | let chapters;
383 | try {
384 | results = await Chapter.findOne({novel: id})
385 | chapters = results.chapters
386 | } catch (e) {
387 | Handle.sendEmail(e.message)
388 | ctx.throw(422, e.message)
389 | }
390 | if (order === '-1') {
391 | chapters = chapters.reverse()
392 | }
393 | ctx.body = {
394 | chapters
395 | }
396 | }
397 |
398 | // export async function createNovel (ctx) {
399 | // const novel = new Novel(ctx.request.body.novel)
400 | // try {
401 | // await novel.save()
402 | // } catch (err) {
403 | // ctx.throw(422, err.message)
404 | // }
405 | //
406 | // ctx.body = {
407 | // success: true
408 | // }
409 | // }
410 | //
411 | // export async function searchNovel (ctx) {
412 | // const keyword = ctx.query.keyword
413 | //
414 | // const url = `http://zhannei.baidu.com/cse/search?q=${keyword}&s=2041213923836881982`
415 | //
416 | // if (keyword) {
417 | // var list = await Crawler.getSearchLists(encodeURI(url))
418 | // }
419 | // else {
420 | // var list = ''
421 | // }
422 | //
423 | // ctx.body = {
424 | // list: list
425 | // }
426 | // }
427 |
--------------------------------------------------------------------------------