小说管理系统总项目

master
tompoojang 10 months ago
parent 05c8f24ffd
commit 03c7ada0d7

9
.gitattributes vendored

@ -0,0 +1,9 @@
*.md linguist-language=Java
*.yml linguist-language=Java
*.html linguist-language=Java
*.js linguist-language=Java
*.xml linguist-language=Java
*.css linguist-language=Java
*.sql linguist-language=Java
*.uml linguist-language=Java
*.cmd linguist-language=Java

7
.gitignore vendored

@ -0,0 +1,7 @@
**/logs
**/.idea
**/cachedata
**/target
**/*.iml
**/.DS_Store

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -0,0 +1,77 @@
<p align="center">
<a href="https://www.swiftproxy.net/?code=T2WV1VT50"><img src="https://xxyopen.com/images/ad1.png" alt="AD" ></a>
<a href="https://cloud.tencent.com/act/cps/redirect?redirect=2446&cps_key=736e609d66e0ac4e57813316cec6fd0b&from=console"><img src="https://youdoc.github.io/img/tencent.jpg" alt="AD" ></a>
</p>
<p align="center">
<a href='https://github.com/201206030/novel-plus'><img alt="Github stars" src="https://img.shields.io/github/stars/201206030/novel-plus?logo=github"></a>
<a href='https://github.com/201206030/novel-plus'><img alt="Github forks" src="https://img.shields.io/github/forks/201206030/novel-plus?logo=github"></a>
<a href='https://gitee.com/novel_dev_team/novel-plus'><img alt="Gitee stars" src="https://gitee.com/novel_dev_team/novel-plus/badge/star.svg?theme=gitee"></a>
<a href='https://gitee.com/novel_dev_team/novel-plus'><img alt="Gitee forks" src="https://gitee.com/novel_dev_team/novel-plus/badge/fork.svg?theme=gitee"></a>
</p>
<p align="center">
👉 <a href='https://novel.xxyopen.com'>官网</a> | 👉 <a href='https://www.bilibili.com/video/BV1Zo4y187Mi'>项目演示</a> | 👉 <a href='https://docs.xxyopen.com/course/novelplus/1.html'>安装教程</a>
</p>
##
novel-plus PCWAP CMS
TXT
##
- [GitHub](https://github.com/201206030/novel) [码云](https://gitee.com/novel_dev_team/novel)
[](https://docs.xxyopen.com)
- ****[GitHub](https://github.com/201206030/novel-plus) [码云](https://gitee.com/novel_dev_team/novel-plus)
- [GitHub](https://github.com/201206030/novel-cloud) [码云](https://gitee.com/novel_dev_team/novel-cloud)
##
```
novel-plus --
novel-common --
novel-front -- &
novel-crawl --
novel-admin --
templates --
```
##
| |
|---------------------| ---------------------------
| Spring Boot | Spring
| Spring AI | Spring AI
| MyBatis | ORM
| MyBatis Dynamic SQL | Mybatis sql
| PageHelper | MyBatis
| MyBatis Generator |
| Sharding-JDBC |
| JJWT | JWT
| Spring Security |
| Apache Shiro |
| Redis |
| Aliyun OSS |
| Lombok |
| Docker |
| MySQL |
| Thymeleaf |
| Layui | UI
##
### 绿
[![](https://www.xxyopen.com/images/green_novel.png)](https://www.xxyopen.com/images/green_novel.png)
[![](https://www.xxyopen.com/images/resource/os/novel-plus/green3.png)](https://www.xxyopen.com/images/resource/os/novel-plus/green3.png)
[![](https://www.xxyopen.com/images/resource/os/novel-plus/green2.png)](https://www.xxyopen.com/images/resource/os/novel-plus/green2.png)
##
https://www.bilibili.com/video/BV18e41197xs
##
使

@ -0,0 +1,53 @@
mode:
#
type: Standalone
#
repository:
#
type: JDBC
#
dataSources:
ds_1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
jdbcUrl: jdbc:mysql://localhost:3306/novel_plus?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
ds_2:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/information_schema?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
#
rules:
- !SINGLE
tables:
- "*.*"
- !SHARDING
tables: #
book_content:
# 使
actualDataNodes: ds_${1}.book_content${0..9}
#
tableStrategy:
standard:
#
shardingColumn: index_id
#
shardingAlgorithmName: bookContentSharding
shardingAlgorithms:
bookContentSharding:
# 使 Groovy SQL = IN
type: INLINE
props:
#
algorithm-expression: book_content${index_id % 10}
props:
# SQL
sql-show: true

@ -0,0 +1,82 @@
PUT /novel
{
"mappings" : {
"book" : {
"properties" : {
"id" : {
"type" : "long"
},
"authorId" : {
"type" : "long"
},
"authorName" : {
"type" : "text",
"analyzer": "ik_smart",
"boost": 1.9
},
"bookName" : {
"type" : "text",
"analyzer": "ik_smart",
"boost": 2
},
"bookDesc" : {
"type" : "text",
"analyzer": "ik_smart",
"boost": 0.1
},
"bookStatus" : {
"type" : "short"
},
"catId" : {
"type" : "integer"
},
"catName" : {
"type" : "text",
"analyzer": "ik_smart",
"boost": 1.0
},
"lastIndexId" : {
"type" : "long"
},
"lastIndexName" : {
"type" : "text",
"analyzer": "ik_smart",
"boost": 0.1
},
"lastIndexUpdateTime" : {
"type": "keyword"
},
"picUrl" : {
"type" : "keyword"
},
"score" : {
"type" : "float"
},
"wordCount" : {
"type" : "integer"
},
"workDirection" : {
"type" : "short"
},
"visitCount" : {
"type": "long"
}
}
}
}
}

@ -0,0 +1,33 @@
/*
Navicat MySQL Data Transfer
Source Server : localhost
Source Server Version : 50725
Source Host : localhost:3306
Source Database : novel_plus
Target Server Type : MYSQL
Target Server Version : 50725
File Encoding : 65001
Date: 2020-05-11 17:56:23
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for order_pay
-- ----------------------------
DROP TABLE IF EXISTS `order_pay`;
CREATE TABLE `order_pay` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '',
`out_trade_no` bigint(20) NOT NULL COMMENT '',
`trade_no` varchar(64) DEFAULT NULL COMMENT '/',
`pay_channel` tinyint(1) NOT NULL DEFAULT '1' COMMENT '12',
`total_amount` int(11) NOT NULL COMMENT '()',
`user_id` bigint(20) NOT NULL COMMENT 'ID',
`pay_status` tinyint(1) DEFAULT '2' COMMENT '012',
`create_time` datetime DEFAULT NULL COMMENT '',
`update_time` datetime DEFAULT NULL COMMENT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4 COMMENT='';

@ -0,0 +1,87 @@
/*
Navicat MySQL Data Transfer
Source Server : localhost
Source Server Version : 50624
Source Host : localhost:3306
Source Database : novel_plus
Target Server Type : MYSQL
Target Server Version : 50624
File Encoding : 65001
Date: 2020-05-13 21:42:20
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for author
-- ----------------------------
DROP TABLE IF EXISTS `author`;
CREATE TABLE `author` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '',
`user_id` bigint(20) DEFAULT NULL COMMENT 'ID',
`invite_code` varchar(20) DEFAULT NULL COMMENT '',
`pen_name` varchar(20) DEFAULT NULL COMMENT '',
`tel_phone` varchar(20) DEFAULT NULL COMMENT '',
`chat_account` varchar(50) DEFAULT NULL COMMENT 'QQ',
`email` varchar(50) DEFAULT NULL COMMENT '',
`work_direction` tinyint(4) DEFAULT NULL COMMENT '01',
`status` tinyint(4) DEFAULT '0' COMMENT '01',
`create_time` datetime DEFAULT NULL COMMENT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='';
-- ----------------------------
-- Records of author
-- ----------------------------
INSERT INTO `author` VALUES ('1', null, 'reerer', 'abc', '13560487656', '23484388', '23484388@qq.com', '0', '0', null);
INSERT INTO `author` VALUES ('2', '1255060328322027520', 'rwrr445554', '', '13560421324', '1179705413', 'reerer@qq.com', '0', '0', '2020-05-13 14:01:31');
-- ----------------------------
-- Table structure for author_code
-- ----------------------------
DROP TABLE IF EXISTS `author_code`;
CREATE TABLE `author_code` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '',
`invite_code` varchar(100) DEFAULT NULL COMMENT '',
`validity_time` datetime DEFAULT NULL COMMENT '',
`is_use` tinyint(1) DEFAULT '0' COMMENT '使0使1:使',
`create_time` datetime DEFAULT NULL COMMENT '',
`create_user_id` bigint(20) DEFAULT NULL COMMENT 'ID',
PRIMARY KEY (`id`),
UNIQUE KEY `key_code` (`invite_code`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COMMENT='';
-- ----------------------------
-- Records of author_code
-- ----------------------------
INSERT INTO `author_code` VALUES ('3', 'reerer', '2020-05-27 22:43:45', '1', '2020-05-13 11:40:56', '1');
INSERT INTO `author_code` VALUES ('4', '123456', '2020-05-28 00:00:00', '0', '2020-05-13 14:09:55', '1');
INSERT INTO `author_code` VALUES ('5', 'ww34343', '2020-05-21 00:00:00', '0', '2020-05-13 14:18:58', '1');
-- ----------------------------
-- Table structure for user_buy_record
-- ----------------------------
DROP TABLE IF EXISTS `user_buy_record`;
CREATE TABLE `user_buy_record` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '',
`user_id` bigint(20) NOT NULL COMMENT 'ID',
`book_id` bigint(20) DEFAULT NULL COMMENT 'ID',
`book_name` varchar(50) DEFAULT NULL COMMENT '',
`book_index_id` bigint(20) DEFAULT NULL COMMENT 'ID',
`book_index_name` varchar(100) DEFAULT NULL COMMENT '',
`buy_amount` int(11) DEFAULT NULL COMMENT '使',
`create_time` datetime DEFAULT NULL COMMENT '',
PRIMARY KEY (`id`),
UNIQUE KEY `key_userId_indexId` (`user_id`,`book_index_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='';
-- ----------------------------
-- Records of user_buy_record
-- ----------------------------
INSERT INTO `user_buy_record` VALUES ('1', '1255060328322027520', '1260400284744613890', '2', '1260522024606953472', '', '10', '2020-05-13 21:29:09');
INSERT INTO `user_buy_record` VALUES ('2', '1255060328322027520', '1260400284744613890', '2', '1260564410687107072', '', '10', '2020-05-13 21:40:38');

@ -0,0 +1,3 @@
INSERT INTO `crawl_source` (`id`, `source_name`, `crawl_rule`, `source_status`, `create_time`, `update_time`) VALUES ('4', '', '{\r\n \"bookListUrl\": \"http://m.shuquge.com/sort/{catId}/0_{page}.html\",\r\n \"catIdRule\": {\r\n \"catId1\": \"1\",\r\n \"catId2\": \"2\",\r\n \"catId3\": \"3\",\r\n \"catId4\": \"4\",\r\n \"catId5\": \"7\",\r\n \"catId6\": \"6\",\r\n \"catId7\": \"8\"\r\n },\r\n \"bookIdPatten\": \"href=\\\"/s/(\\\\d+)\\\\.html\\\"\",\r\n \"pagePatten\": \"第(\\\\d+)/\\\\d+页\",\r\n \"totalPagePatten\": \"第\\\\d+/(\\\\d+)页\",\r\n \"bookDetailUrl\": \"http://m.shuquge.com/s/{bookId}.html\",\r\n \"bookNamePatten\": \"<a\\\\s+href=\\\"/s/\\\\d+\\\\.html\\\"><h2>([^/]+)</h2></a>\",\r\n \"authorNamePatten\": \"<p>作者:([^/]+)</p>\",\r\n \"picUrlPatten\": \"src=\\\"(http://www.shuquge.com/files/article/image/\\\\d+/\\\\d+/\\\\d+s\\\\.jpg)\\\"\",\r\n \"statusPatten\": \"<p>状态:([^/]+)</p>\",\r\n \"bookStatusRule\": {\r\n \"连载中\": 0,\r\n \"完本\": 1\r\n },\r\n \"descStart\": \"<div class=\\\"intro_info\\\">\",\r\n \"descEnd\": \"最新章节推荐地址\",\r\n \"bookIndexUrl\": \"http://www.shuquge.com/txt/{bookId}/index.html\",\r\n \"bookIndexStart\": \"》正文\",\r\n \"indexIdPatten\": \"<dd><a\\\\s+href=\\\"(\\\\d+)\\\\.html\\\">[^/]+</a></dd>\",\r\n \"indexNamePatten\": \"<dd><a\\\\s+href=\\\"\\\\d+\\\\.html\\\">([^/]+)</a></dd>\",\r\n \"bookContentUrl\": \"http://www.shuquge.com/txt/{bookId}/{indexId}.html\",\r\n \"contentStart\": \"<div id=\\\"content\\\" class=\\\"showtxt\\\">\",\r\n \"contentEnd\": \"http://www.shuquge.com\"\r\n}', '1', '2020-05-18 12:02:34', '2020-05-18 12:02:34');
INSERT INTO `crawl_source` (`id`, `source_name`, `crawl_rule`, `source_status`, `create_time`, `update_time`) VALUES ('5', '', '{\"bookListUrl\":\"http://m.mcmssc.com/xclass/{catId}/{page}.html\",\"catIdRule\":{\"catId1\":\"1\",\"catId2\":\"2\",\"catId3\":\"3\",\"catId4\":\"4\",\"catId5\":\"5\",\"catId6\":\"6\",\"catId7\":\"7\"},\"bookIdPatten\":\"href=\\\"/(\\\\d+_\\\\d+)/\\\"\",\"pagePatten\":\"class=\\\"page_txt\\\"\\\\s+value=\\\"(\\\\d+)/\\\\d+\\\"\\\\s+size=\",\"totalPagePatten\":\"class=\\\"page_txt\\\"\\\\s+value=\\\"\\\\d+/(\\\\d+)\\\"\\\\s+size=\",\"bookDetailUrl\":\"http://m.mcmssc.com/{bookId}/\",\"bookNamePatten\":\"<span\\\\s+class=\\\"title\\\">([^/]+)</span>\",\"authorNamePatten\":\"<a\\\\s+href=\\\"/author/\\\\d+/\\\">([^/]+)</a>\",\"picUrlPatten\":\"<img\\\\s+src=\\\"([^>]+)\\\"\\\\s+onerror=\",\"picUrlPrefix\":\"http://m.mcmssc.com/\",\"statusPatten\":\">状态:([^/]+)<\",\"bookStatusRule\":{\"连载\":0,\"全本\":1},\"visitCountPatten\":\">点击:(\\\\d+)<\",\"descStart\":\"<p class=\\\"review\\\">\",\"descEnd\":\"</p>\",\"bookIndexUrl\":\"http://m.mcmssc.com/{bookId}/all.html\",\"indexIdPatten\":\"<a\\\\s+href=\\\"/\\\\d+_\\\\d+/(\\\\d+)\\\\.html\\\">[^/]+</a>\",\"indexNamePatten\":\"<a\\\\s+href=\\\"/\\\\d+_\\\\d+/\\\\d+\\\\.html\\\">([^/]+)</a>\",\"bookContentUrl\":\"http://www.mcmssc.com/{bookId}/{indexId}.html\",\"contentStart\":\"</p>\",\"contentEnd\":\"<div align=\\\"center\\\">\"}', '1', '2020-05-18 15:57:41', '2020-05-18 15:57:41');
UPDATE `crawl_source` SET `source_name` = '', `crawl_rule` = '{\n \"bookListUrl\": \"http://m.shuquge.com/sort/{catId}/0_{page}.html\",\n \"catIdRule\": {\n \"catId1\": \"1\",\n \"catId2\": \"2\",\n \"catId3\": \"3\",\n \"catId4\": \"4\",\n \"catId5\": \"7\",\n \"catId6\": \"6\",\n \"catId7\": \"8\"\n },\n \"bookIdPatten\": \"href=\\\"/s/(\\\\d+)\\\\.html\\\"\",\n \"pagePatten\": \"第(\\\\d+)/\\\\d+页\",\n \"totalPagePatten\": \"第\\\\d+/(\\\\d+)页\",\n \"bookDetailUrl\": \"http://m.shuquge.com/s/{bookId}.html\",\n \"bookNamePatten\": \"<a\\\\s+href=\\\"/s/\\\\d+\\\\.html\\\"><h2>([^/]+)</h2></a>\",\n \"authorNamePatten\": \"<p>作者:([^/]+)</p>\",\n \"picUrlPatten\": \"src=\\\"(http://www.shuquge.com/files/article/image/\\\\d+/\\\\d+/\\\\d+s\\\\.jpg)\\\"\",\n \"statusPatten\": \"<p>状态:([^/]+)</p>\",\n \"bookStatusRule\": {\n \"连载中\": 0,\n \"完本\": 1\n },\n \"descStart\": \"<div class=\\\"intro_info\\\">\",\n \"descEnd\": \"最新章节推荐地址\",\n \"bookIndexUrl\": \"http://www.shuquge.com/txt/{bookId}/index.html\",\n \"bookIndexStart\": \"<dt>《\",\n \"indexIdPatten\": \"<dd><a\\\\s+href=\\\"(\\\\d+)\\\\.html\\\">[^/]+</a></dd>\",\n \"indexNamePatten\": \"<dd><a\\\\s+href=\\\"\\\\d+\\\\.html\\\">([^/]+)</a></dd>\",\n \"bookContentUrl\": \"http://www.shuquge.com/txt/{bookId}/{indexId}.html\",\n \"contentStart\": \"<div id=\\\"content\\\" class=\\\"showtxt\\\">\",\n \"contentEnd\": \"http://www.shuquge.com\"\n}', `source_status` = 1, `create_time` = '2020-05-18 12:02:34', `update_time` = '2020-05-18 12:02:34' WHERE `id` = 4;

@ -0,0 +1,2 @@
INSERT INTO `crawl_source` (`id`, `source_name`, `crawl_rule`, `source_status`, `create_time`, `update_time`) VALUES
(6, '', '{\n \"bookListUrl\": \"http://www.xbiquge.la/fenlei/{catId}_{page}.html\",\n \"catIdRule\": {\n \"catId1\": \"1\",\n \"catId2\": \"2\",\n \"catId3\": \"3\",\n \"catId4\": \"4\",\n \"catId5\": \"6\",\n \"catId6\": \"5\"\n },\n \"bookIdPatten\": \"<a\\\\s+href=\\\"http://www.xbiquge.la/(\\\\d+/\\\\d+)/\\\"\\\\s+target=\\\"_blank\\\">\",\n \"pagePatten\": \"<em\\\\s+id=\\\"pagestats\\\">(\\\\d+)/\\\\d+</em>\",\n \"totalPagePatten\": \"<em\\\\s+id=\\\"pagestats\\\">\\\\d+/(\\\\d+)</em>\",\n \"bookDetailUrl\": \"http://www.xbiquge.la/{bookId}/\",\n \"bookNamePatten\": \"<h1>([^/]+)</h1>\",\n \"authorNamePatten\": \"者:([^/]+)</p>\",\n \"picUrlPatten\": \"src=\\\"(http://www.xbiquge.la/files/article/image/\\\\d+/\\\\d+/\\\\d+s\\\\.jpg)\\\"\",\n \"bookStatusRule\": {},\n \"descStart\": \"<div id=\\\"intro\\\">\",\n \"descEnd\": \"</div>\",\n \"upadateTimePatten\": \"<p>最后更新:(\\\\d+-\\\\d+-\\\\d+\\\\s\\\\d+:\\\\d+:\\\\d+)</p>\",\n \"upadateTimeFormatPatten\": \"yyyy-MM-dd HH:mm:ss\",\n \"bookIndexUrl\": \"http://www.xbiquge.la/{bookId}/\",\n \"indexIdPatten\": \"<a\\\\s+href=\'/\\\\d+/\\\\d+/(\\\\d+)\\\\.html\'\\\\s+>[^/]+</a>\",\n \"indexNamePatten\": \"<a\\\\s+href=\'/\\\\d+/\\\\d+/\\\\d+\\\\.html\'\\\\s+>([^/]+)</a>\",\n \"bookContentUrl\": \"http://www.xbiquge.la/{bookId}/{indexId}.html\",\n \"contentStart\": \"<div id=\\\"content\\\">\",\n \"contentEnd\": \"<p>\"\n}', 0, '2020-05-23 22:46:58', '2020-05-23 22:46:58');

@ -0,0 +1,40 @@
/*
Navicat MySQL Data Transfer
Source Server : localhost
Source Server Version : 50725
Source Host : localhost:3306
Source Database : novel_plus
Target Server Type : MYSQL
Target Server Version : 50725
File Encoding : 65001
Date: 2020-06-15 15:06:55
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for crawl_single_task
-- ----------------------------
DROP TABLE IF EXISTS `crawl_single_task`;
CREATE TABLE `crawl_single_task` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '',
`source_id` int(11) DEFAULT NULL COMMENT 'ID',
`source_name` varchar(50) DEFAULT NULL COMMENT '',
`source_book_id` varchar(255) DEFAULT NULL COMMENT 'ID',
`cat_id` int(11) DEFAULT NULL COMMENT 'ID',
`book_name` varchar(50) DEFAULT NULL COMMENT '',
`author_name` varchar(50) DEFAULT NULL COMMENT '',
`task_status` tinyint(1) DEFAULT '2' COMMENT '012',
`exc_count` tinyint(2) DEFAULT '0' COMMENT '5',
`create_time` datetime DEFAULT NULL COMMENT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COMMENT='';
-- ----------------------------
-- Records of crawl_single_task
-- ----------------------------
INSERT INTO `crawl_single_task` VALUES ('6', '2', '', '1', '1', '1', '1', '0', '5', '2020-06-15 14:36:07');
INSERT INTO `crawl_single_task` VALUES ('7', '5', '', '108_108291', '1', '', '', '1', '1', '2020-06-15 14:46:08');

@ -0,0 +1,27 @@
CREATE TABLE `author_income_detail` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '',
`user_id` bigint(20) NOT NULL COMMENT 'ID',
`author_id` bigint(20) NOT NULL COMMENT 'ID',
`book_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'ID,0',
`income_date` date NOT NULL COMMENT '',
`income_account` int(11) NOT NULL DEFAULT '0' COMMENT '',
`income_count` int(11) NOT NULL DEFAULT '0' COMMENT '',
`income_number` int(11) NOT NULL DEFAULT '0' COMMENT '',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='稿';
CREATE TABLE `author_income` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '',
`user_id` bigint(20) NOT NULL COMMENT 'ID',
`author_id` bigint(20) NOT NULL COMMENT 'ID',
`book_id` bigint(20) NOT NULL COMMENT 'ID',
`income_month` date NOT NULL COMMENT '',
`pre_tax_income` bigint(20) NOT NULL DEFAULT '0' COMMENT '',
`after_tax_income` bigint(20) NOT NULL DEFAULT '0' COMMENT '',
`pay_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '01',
`confirm_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '稿01',
`detail` varchar(255) DEFAULT NULL COMMENT '',
`create_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='稿';

@ -0,0 +1 @@
alter table book add column `yesterday_buy` int(11) DEFAULT '0' COMMENT '' after comment_count;

@ -0,0 +1 @@
alter table book_index add column `book_price` int(3) DEFAULT 0 COMMENT '' after `is_vip`;

@ -0,0 +1,32 @@
INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`, `gmt_create`, `gmt_modified`) VALUES (246, 241, '', NULL, 'novel:news:batchRemove', 2, NULL, 6, NULL, NULL);
INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`, `gmt_create`, `gmt_modified`) VALUES (245, 241, '', NULL, 'novel:news:remove', 2, NULL, 6, NULL, NULL);
INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`, `gmt_create`, `gmt_modified`) VALUES (244, 241, '', NULL, 'novel:news:edit', 2, NULL, 6, NULL, NULL);
INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`, `gmt_create`, `gmt_modified`) VALUES (243, 241, '', NULL, 'novel:news:add', 2, NULL, 6, NULL, NULL);
INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`, `gmt_create`, `gmt_modified`) VALUES (242, 241, '', NULL, 'novel:news:detail', 2, NULL, 6, NULL, NULL);
INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`, `gmt_create`, `gmt_modified`) VALUES (241, 234, '', 'novel/news', 'novel:news:news', 1, 'fa', 8, NULL, NULL);
INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`, `gmt_create`, `gmt_modified`) VALUES (240, 235, '', NULL, 'novel:category:batchRemove', 2, NULL, 6, NULL, NULL);
INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`, `gmt_create`, `gmt_modified`) VALUES (239, 235, '', NULL, 'novel:category:remove', 2, NULL, 6, NULL, NULL);
INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`, `gmt_create`, `gmt_modified`) VALUES (238, 235, '', NULL, 'novel:category:edit', 2, NULL, 6, NULL, NULL);
INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`, `gmt_create`, `gmt_modified`) VALUES (237, 235, '', NULL, 'novel:category:add', 2, NULL, 6, NULL, NULL);
INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`, `gmt_create`, `gmt_modified`) VALUES (236, 235, '', NULL, 'novel:category:detail', 2, NULL, 6, NULL, NULL);
INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`, `gmt_create`, `gmt_modified`) VALUES (235, 234, '', 'novel/category', 'novel:category:category', 1, 'fa', 6, NULL, NULL);
INSERT INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`, `gmt_create`, `gmt_modified`) VALUES (234, 0, '', '', '', 0, 'fa fa-newspaper-o', 8, NULL, NULL);
INSERT INTO `sys_role_menu`(`id`, `role_id`, `menu_id`) VALUES (4889, 1, 246);
INSERT INTO `sys_role_menu`(`id`, `role_id`, `menu_id`) VALUES (4890, 1, 245);
INSERT INTO `sys_role_menu`(`id`, `role_id`, `menu_id`) VALUES (4891, 1, 244);
INSERT INTO `sys_role_menu`(`id`, `role_id`, `menu_id`) VALUES (4892, 1, 243);
INSERT INTO `sys_role_menu`(`id`, `role_id`, `menu_id`) VALUES (4893, 1, 242);
INSERT INTO `sys_role_menu`(`id`, `role_id`, `menu_id`) VALUES (4899, 1, 241);
INSERT INTO `sys_role_menu`(`id`, `role_id`, `menu_id`) VALUES (4894, 1, 240);
INSERT INTO `sys_role_menu`(`id`, `role_id`, `menu_id`) VALUES (4895, 1, 239);
INSERT INTO `sys_role_menu`(`id`, `role_id`, `menu_id`) VALUES (4896, 1, 238);
INSERT INTO `sys_role_menu`(`id`, `role_id`, `menu_id`) VALUES (4897, 1, 237);
INSERT INTO `sys_role_menu`(`id`, `role_id`, `menu_id`) VALUES (4898, 1, 236);
INSERT INTO `sys_role_menu`(`id`, `role_id`, `menu_id`) VALUES (4900, 1, 235);
INSERT INTO `sys_role_menu`(`id`, `role_id`, `menu_id`) VALUES (4888, 1, 234);
delete from sys_menu where menu_id = 202;

@ -0,0 +1 @@
alter table news add column `read_count` BIGINT NOT NULL DEFAULT '0' COMMENT '' after content;

@ -0,0 +1,3 @@
alter table book_index add column storage_type varchar(10) NOT NULL DEFAULT 'db' COMMENT '' after book_price ;

@ -0,0 +1,121 @@
CREATE TABLE `website_info`
(
id bigint(20) NOT NULL AUTO_INCREMENT COMMENT '',
name varchar(50) NOT NULL COMMENT '',
domain varchar(50) NOT NULL COMMENT '',
keyword varchar(50) NOT NULL COMMENT 'SEO',
description varchar(512) NOT NULL COMMENT '',
qq varchar(20) NOT NULL COMMENT 'QQ',
logo varchar(200) NOT NULL COMMENT 'logo',
logo_dark varchar(200) NOT NULL COMMENT 'logo',
create_time datetime null comment '',
create_user_id bigint null comment 'ID',
update_time datetime null comment '',
update_user_id bigint null comment 'ID',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4 COMMENT ='';
INSERT INTO website_info (id, name, domain, keyword, description, qq, logo, logo_dark, create_time, create_user_id,
update_time, update_user_id)
VALUES (1, '', 'www.xxyopen.com', ',,CMS,,,',
'PCWAPCMS',
'1179705413', 'https://youdoc.gitee.io/resource/images/logo/logo.png',
'https://youdoc.gitee.io/resource/images/logo/logo_white.png', null, null, null, null);
INSERT INTO sys_menu (menu_id, parent_id, name, url, perms, type, icon, order_num, gmt_create, gmt_modified)
VALUES (300, 0, '', '', '', 0, 'fa fa-television', 6, null, null);
INSERT
INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (301, 300, '', 'novel/websiteInfo', 'novel:websiteInfo:websiteInfo', '1', 'fa', '6');
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 300);
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 301);
INSERT INTO `sys_menu` (menu_id, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (310, 300, '', 'novel/friendLink', 'novel:friendLink:friendLink', '1', 'fa', '16');
INSERT INTO `sys_menu` (menu_id, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (311, 310, '', null, 'novel:friendLink:detail', '2', null, '6');
INSERT INTO `sys_menu` (menu_id, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (312, 310, '', null, 'novel:friendLink:add', '2', null, '6');
INSERT INTO `sys_menu` (menu_id, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (313, 310, '', null, 'novel:friendLink:edit', '2', null, '6');
INSERT INTO `sys_menu` (menu_id, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (314, 310, '', null, 'novel:friendLink:remove', '2', null, '6');
INSERT INTO `sys_menu` (menu_id, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (315, 310, '', null, 'novel:friendLink:batchRemove', '2', null, '6');
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 310);
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 311);
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 312);
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 313);
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 314);
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 315);
INSERT INTO sys_menu (menu_id, parent_id, name, url, perms, type, icon, order_num, gmt_create, gmt_modified)
VALUES (400, 0, '', '', '', 0, 'fa fa-vcard', 9, null, null);
INSERT
INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (401, 400, '', 'novel/user', 'novel:user:user', '1', 'fa', '6');
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 400);
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 401);
INSERT INTO sys_menu (menu_id, parent_id, name, url, perms, type, icon, order_num, gmt_create, gmt_modified)
VALUES (500, 0, '', '', '', 0, 'fa fa-money', 19, null, null);
INSERT
INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (501, 500, '', 'novel/pay', 'novel:pay:pay', '1', 'fa', '6');
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 500);
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 501);
INSERT INTO sys_menu (menu_id, parent_id, name, url, perms, type, icon, order_num, gmt_create, gmt_modified)
VALUES (600, 0, '', '', '', 0, 'fa fa-book', 15, null, null);
INSERT
INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (601, 600, '', 'novel/book', 'novel:book:book', '1', 'fa', '6');
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 600);
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 601);
INSERT INTO `sys_menu` (menu_id, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (602, 601, '', null, 'novel:book:remove', '2', null, '6');
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 602);
INSERT
INTO `sys_menu`(`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (603, 600, '', 'novel/bookComment', 'novel:bookComment:bookComment', '1', 'fa', '10');
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 603);
INSERT INTO `sys_menu` (menu_id, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (604, 603, '', null, 'novel:bookComment:remove', '2', null, '6');
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 604);

@ -0,0 +1,51 @@
INSERT INTO sys_dict (name, value, type, description, sort, parent_id, create_by, create_date, update_by,
update_date, remarks, del_flag)
VALUES ('', '0', 'book_rec_type', '', 0, null, null, null, null, null, '', null);
INSERT INTO sys_dict (name, value, type, description, sort, parent_id, create_by, create_date, update_by,
update_date, remarks, del_flag)
VALUES ('', '1', 'book_rec_type', '', 1, null, null, null, null, null, '', null);
INSERT INTO sys_dict (name, value, type, description, sort, parent_id, create_by, create_date, update_by,
update_date, remarks, del_flag)
VALUES ('', '2', 'book_rec_type', '', 2, null, null, null, null, null, '', null);
INSERT INTO sys_dict (name, value, type, description, sort, parent_id, create_by, create_date, update_by,
update_date, remarks, del_flag)
VALUES ('', '3', 'book_rec_type', '', 3, null, null, null, null, null, '', null);
INSERT INTO sys_dict (name, value, type, description, sort, parent_id, create_by, create_date, update_by,
update_date, remarks, del_flag)
VALUES ('', '4', 'book_rec_type', '', 4, null, null, null, null, null, '', null);
INSERT INTO `sys_menu` (`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (320, '300', '', 'novel/bookSetting', 'novel:bookSetting:bookSetting', '1', 'fa', '6');
INSERT INTO `sys_menu` (`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (321, '320', '', null, 'novel:bookSetting:detail', '2', null, '6');
INSERT INTO `sys_menu` (`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (322, '320', '', null, 'novel:bookSetting:add', '2', null, '6');
INSERT INTO `sys_menu` (`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (323, '320', '', null, 'novel:bookSetting:edit', '2', null, '6');
INSERT INTO `sys_menu` (`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (324, '320', '', null, 'novel:bookSetting:remove', '2', null, '6');
INSERT INTO `sys_menu` (`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (325, '320', '', null, 'novel:bookSetting:batchRemove', '2', null, '6');
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 320);
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 321);
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 322);
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 323);
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 324);
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 325);
INSERT INTO `sys_menu` (`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (410, '400', '', 'novel/userFeedback', 'novel:userFeedback:userFeedback', '1', 'fa', '16');
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 410);

@ -0,0 +1,4 @@
INSERT INTO `sys_menu` (`menu_id`, `parent_id`, `name`, `url`, `perms`, `type`, `icon`, `order_num`)
VALUES (305, '301', '', null, 'novel:websiteInfo:edit', '2', null, '6');
INSERT INTO sys_role_menu (role_id, menu_id)
VALUES (1, 305);

@ -0,0 +1,3 @@
update crawl_source
set crawl_rule = replace(crawl_rule, 'ibiquge.net', 'ibiquzw.org')
where id = 16;

@ -0,0 +1,44 @@
INSERT INTO crawl_source (source_name, crawl_rule, source_status, create_time, update_time)
VALUES ('', '{
"bookListUrl": "http://www.xbiqugu.la/fenlei/{catId}_{page}.html",
"catIdRule": {
"catId1": "1",
"catId2": "2",
"catId3": "3",
"catId4": "4",
"catId5": "6",
"catId6": "5"
},
"bookIdPatten": "<a\\\\s+href=\\"http://www.xbiqugu.la/(\\\\d+/\\\\d+)/\\"\\\\s+target=\\"_blank\\">",
"pagePatten": "<em\\\\s+id=\\"pagestats\\">(\\\\d+)/\\\\d+</em>",
"totalPagePatten": "<em\\\\s+id=\\"pagestats\\">\\\\d+/(\\\\d+)</em>",
"bookDetailUrl": "http://www.xbiqugu.la/{bookId}/",
"bookNamePatten": "<h1>([^/]+)</h1>",
"authorNamePatten": "者:([^/]+)</p>",
"picUrlPatten": "src=\\"(http://www.xbiqugu.la/files/article/image/\\\\d+/\\\\d+/\\\\d+s\\\\.jpg)\\"",
"bookStatusRule": {},
"descStart": "<div id=\\"intro\\">",
"descEnd": "</div>",
"upadateTimePatten": "<p>最后更新:(\\\\d+-\\\\d+-\\\\d+\\\\s\\\\d+:\\\\d+:\\\\d+)</p>",
"upadateTimeFormatPatten": "yyyy-MM-dd HH:mm:ss",
"bookIndexUrl": "http://www.xbiqugu.la/{bookId}/",
"indexIdPatten": "<a\\\\s+href=''/\\\\d+/\\\\d+/(\\\\d+)\\\\.html''\\\\s+>[^/]+</a>",
"indexNamePatten": "<a\\\\s+href=''/\\\\d+/\\\\d+/\\\\d+\\\\.html''\\\\s+>([^/]+)</a>",
"bookContentUrl": "http://www.xbiqugu.la/{bookId}/{indexId}.html",
"contentStart": "<div id=\\"content\\">",
"contentEnd": "<p>",
"filterContent":"<div\\\\s+id=\\"content_tip\\">\\\\s*<b>([^/]+)</b>\\\\s*</div>"
}', 0, '2024-06-01 10:11:39', '2024-06-01 10:11:39');
update crawl_source
set crawl_rule = replace(crawl_rule, 'ibiquzw.org', 'biquxs.info')
where id = 16;
delete
from sys_menu
where menu_id = 104;
delete
from sys_menu
where menu_id = 57;

@ -0,0 +1,2 @@
FROM mysql:8.0
COPY novel_plus.sql /docker-entrypoint-initdb.d/init.sql

File diff suppressed because it is too large Load Diff

@ -0,0 +1,3 @@
1. novel_plus.sql sql yyyyMMdd.sql sql
2. novel_plus.sql
3. sql sql sql

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>novel</artifactId>
<groupId>com.java2nb</groupId>
<version>5.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>novel-crawl</artifactId>
<name>novel-crawl</name>
<description> </description>
<dependencies>
<dependency>
<groupId>com.java2nb</groupId>
<artifactId>novel-common</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<!-- -->
<copy todir="${project.build.directory}/build/config" overwrite="true">
<fileset dir="${basedir}/src/main/build/config">
<include name="*.*"/>
</fileset>
</copy>
<copy todir="${project.build.directory}/build/config" overwrite="true">
<fileset dir="${basedir}/../config">
<include name="*.*"/>
</fileset>
</copy>
<copy file="${project.build.directory}/${project.artifactId}-${project.version}.jar"
tofile="${project.build.directory}/build/${project.artifactId}.jar"/>
<fixcrlf srcdir="${basedir}/src/main/build/scripts" eol="unix"/>
<copy todir="${project.build.directory}/build/bin">
<fileset dir="${basedir}/src/main/build/scripts">
<include name="*.sh"/>
<include name="*.txt"/>
<include name="*.bat"/>
</fileset>
</copy>
<zip destfile='${project.build.directory}/build/${project.artifactId}.zip'>
<zipfileset filemode="755" dir='${project.build.directory}/build/'/>
</zip>
<copy file="${basedir}/src/main/build/docker/Dockerfile"
tofile="${project.build.directory}/build/Dockerfile"/>
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,47 @@
#
server:
port: 8083
spring:
data:
redis:
#RedisIP
host: 127.0.0.1
#Redis
port: 6379
#Redis
password: 123456
#
timeout: 10000
#
admin:
username: admin
password: admin
#
##线
##1
##CPU线
crawl:
update:
thread: 1
#
content:
save:
storage: db # db-txt-TXT
path: /Users/xiongxiaoyang/books # TXT
# HTTP
http:
proxy:
# HTTP true-false-
enabled: false
# IP
ip: us.swiftproxy.net
#
port: 7878
#
username: swiftproxy_u
#
password: swiftproxy_p

@ -0,0 +1,9 @@
FROM openjdk:8
ADD novel-crawl.jar /root
ENV dburl=""
ENV username=""
ENV password=""
ENV redishost = ""
ENV redisport = ""
ENV redispwd = ""
ENTRYPOINT ["sh","-c","java -Dspring.datasource.url=${dburl} -Dspring.datasource.username=${username} -Dspring.datasource.password=${password} -Dspring.redis.host=${redishost} -Dspring.redis.port=${redisport} -Dspring.redis.password=${redispwd} -jar /root/novel-crawl.jar"]

@ -0,0 +1,94 @@
#!/bin/sh
APP_NAME=novel-crawl
JAR_NAME=$APP_NAME\.jar
#PID 代表是PID文件
PID=$APP_NAME\.pid
#使用说明,用来提示输入参数
usage() {
echo "Usage: ./novel-crawl.sh [start|stop|restart|status]"
exit 1
}
#检查程序是否在运行
is_exist(){
pid=`ps -ef|grep $JAR_NAME|grep -v grep|awk '{print $2}' `
#如果不存在返回1存在返回0
if [ -z "${pid}" ]; then
return 1
else
return 0
fi
}
#启动方法
start(){
is_exist
if [ $? -eq "0" ]; then
echo ">>> 小说精品屋爬虫正在运行 PID = ${pid} <<<"
else
echo ">>> 小说精品屋爬虫开始启动 <<<"
nohup java -jar -Dspring.profiles.active=prod $JAR_NAME >/dev/null 2>&1 &
sleep 10
echo $! > $PID
echo ">>> 小说精品屋爬虫启动完成 PID = $! <<<"
status
fi
}
#停止方法
stop(){
#is_exist
pidf=$(cat $PID)
#echo "$pidf"
echo ">>> 小说精品屋爬虫 PID = $pidf 开始停止 <<<"
kill $pidf
rm -rf $PID
sleep 2
is_exist
if [ $? -eq "0" ]; then
echo ">>> 小说精品屋爬虫 PID = $pid 开始强制停止 <<<"
kill -9 $pid
sleep 2
status
else
status
fi
}
#输出运行状态
status(){
is_exist
if [ $? -eq "0" ]; then
echo ">>> 小说精品屋爬虫正在运行 PID = ${pid} <<<"
else
echo ">>> 小说精品屋爬虫没有运行 <<<"
fi
}
#重启
restart(){
stop
start
}
#根据输入参数,选择执行对应方法,不输入则执行使用说明
case "$1" in
"start")
start
;;
"stop")
stop
;;
"status")
status
;;
"restart")
restart
;;
*)
usage
;;
esac
exit 0

@ -0,0 +1,39 @@
package com.java2nb.novel;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import java.net.InetAddress;
/**
* @author Administrator
*/
@SpringBootApplication
@EnableCaching
@EnableScheduling
@ServletComponentScan
@MapperScan(basePackages = {"com.java2nb.novel.mapper"})
@Slf4j
public class CrawlNovelApplication {
public static void main(String[] args) {
SpringApplication.run(CrawlNovelApplication.class);
}
@Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
log.info("项目启动啦,访问路径:{}", "http://" + InetAddress.getLocalHost().getHostAddress() + ":" + ctx.getEnvironment().getProperty("server.port"));
};
}
}

@ -0,0 +1,159 @@
package com.java2nb.novel.controller;
import com.java2nb.novel.core.cache.CacheKey;
import com.java2nb.novel.core.cache.CacheService;
import com.java2nb.novel.core.utils.HttpUtil;
import io.github.xxyopen.model.page.PageBean;
import com.java2nb.novel.entity.CrawlSingleTask;
import com.java2nb.novel.entity.CrawlSource;
import com.java2nb.novel.service.CrawlService;
import io.github.xxyopen.model.resp.RestResult;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Administrator
*/
@RestController
@RequestMapping("crawl")
@RequiredArgsConstructor
public class CrawlController {
private final CrawlService crawlService;
private final CacheService cacheService;
/**
*
* */
@PostMapping("addCrawlSource")
public RestResult<Void> addCrawlSource(CrawlSource source){
crawlService.addCrawlSource(source);
return RestResult.ok();
}
/**
*
* */
@GetMapping("listCrawlByPage")
public RestResult<PageBean<CrawlSource>> listCrawlByPage(@RequestParam(value = "curr", defaultValue = "1") int page, @RequestParam(value = "limit", defaultValue = "10") int pageSize){
return RestResult.ok(crawlService.listCrawlByPage(page,pageSize));
}
/**
*
* */
@GetMapping("getCrawlSource/{id}")
public RestResult<CrawlSource> getCrawlSource(@PathVariable("id") Integer id){
CrawlSource crawlSource= crawlService.getCrawlSource(id);
return RestResult.ok(crawlSource);
}
/**
*
* @param rule
* @param url
* @param isRefresh
* @return
*/
@PostMapping("testParse")
public RestResult<Object> testParse(String rule,String url,String isRefresh){
Map<String,Object> resultMap=new HashMap<>();
String html =null;
if(url.startsWith("https://")||url.startsWith("http://")){
String refreshCache="1";
if(!refreshCache.equals(isRefresh)) {
Object cache = cacheService.getObject(CacheKey.BOOK_TEST_PARSE + url);
if (cache == null) {
isRefresh="1";
}else {
html = (String) cache;
}
}
if(refreshCache.equals(isRefresh)){
html = HttpUtil.getByHttpClientWithChrome(url);
if (html != null) {
cacheService.setObject(CacheKey.BOOK_TEST_PARSE + url, html, 60 * 10);
}else{
resultMap.put("msg","html is null");
return RestResult.ok(resultMap);
}
}
}else{
resultMap.put("html","url is null");
return RestResult.ok(resultMap);
}
Pattern pattern = Pattern.compile(rule);
Matcher matcher = pattern.matcher(html);
boolean isFind = matcher.find();
resultMap.put("是否匹配",isFind);
if(isFind){
resultMap.put("匹配结果",matcher.group(1));
}
// resultMap.put("url",url);
return RestResult.ok(resultMap);
}
/**
*
* */
@PostMapping("updateCrawlSource")
public RestResult<Void> updateCrawlSource(CrawlSource source) {
crawlService.updateCrawlSource(source);
return RestResult.ok();
}
/**
*
* */
@PostMapping("openOrCloseCrawl")
public RestResult<Void> openOrCloseCrawl(Integer sourceId,Byte sourceStatus){
crawlService.openOrCloseCrawl(sourceId,sourceStatus);
return RestResult.ok();
}
/**
*
* */
@PostMapping("addCrawlSingleTask")
public RestResult<Void> addCrawlSingleTask(CrawlSingleTask singleTask){
crawlService.addCrawlSingleTask(singleTask);
return RestResult.ok();
}
/**
*
* */
@GetMapping("listCrawlSingleTaskByPage")
public RestResult<PageBean<CrawlSingleTask>> listCrawlSingleTaskByPage(@RequestParam(value = "curr", defaultValue = "1") int page, @RequestParam(value = "limit", defaultValue = "10") int pageSize){
return RestResult.ok(crawlService.listCrawlSingleTaskByPage(page,pageSize));
}
/**
*
* */
@DeleteMapping("delCrawlSingleTask/{id}")
public RestResult<Void> delCrawlSingleTask(@PathVariable("id") Long id){
crawlService.delCrawlSingleTask(id);
return RestResult.ok();
}
}

@ -0,0 +1,43 @@
package com.java2nb.novel.controller.page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author 11797
*/
@Slf4j
@RequiredArgsConstructor
@Controller
public class PageController {
@RequestMapping("{url}.html")
public String module(@PathVariable("url") String url) {
return url;
}
@RequestMapping("{module}/{url}.html")
public String module2(@PathVariable("module") String module, @PathVariable("url") String url) {
return module + "/" + url;
}
@RequestMapping("{module}/{classify}/{url}.html")
public String module3(@PathVariable("module") String module, @PathVariable("classify") String classify,
@PathVariable("url") String url) {
return module + "/" + classify + "/" + url;
}
/**
*
*/
@RequestMapping(path = {"/", "/index", "/index.html"})
public String index() {
return "crawl/crawlSource_list";
}
}

@ -0,0 +1,69 @@
package com.java2nb.novel.core.config;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
/**
* SpringSecurity
*
* @author Administrator
*/
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration {
@Value("${admin.username}")
private String username;
@Value("${admin.password}")
private String password;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public InMemoryUserDetailsManager userDetailsService() {
UserDetails admin = User.builder()
.username(username)
.password(passwordEncoder().encode(password))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(admin);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // 禁用 CSRF
.authorizeHttpRequests(auth -> auth
.requestMatchers("/css/**", "/favicon.ico").permitAll() // 允许访问静态资源
.anyRequest().hasRole("ADMIN") // 其他请求需要 ADMIN 角色
)
.formLogin(form -> form
.loginPage("/login.html") // 自定义登录页面
.loginProcessingUrl("/login") // 登录处理 URL
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout") // 登出 URL
.logoutSuccessUrl("/") // 登出成功后跳转的页面
)
.httpBasic(Customizer.withDefaults()); // 启用 HTTP Basic 认证
return http.build();
}
}

@ -0,0 +1,25 @@
package com.java2nb.novel.core.crawl;
import com.java2nb.novel.entity.BookContent;
import com.java2nb.novel.entity.BookIndex;
import lombok.Data;
import java.util.List;
/**
* bean
* @author Administrator
*/
@Data
public class ChapterBean {
/**
*
* */
List<BookIndex> bookIndexList;
/**
*
* */
List<BookContent> bookContentList;
}

@ -0,0 +1,12 @@
package com.java2nb.novel.core.crawl;
import com.java2nb.novel.entity.Book;
/**
*
* */
public interface CrawlBookChapterHandler {
void handle(ChapterBean chapterBean);
}

@ -0,0 +1,12 @@
package com.java2nb.novel.core.crawl;
import com.java2nb.novel.entity.Book;
/**
*
* */
public interface CrawlBookHandler {
void handle(Book book);
}

@ -0,0 +1,310 @@
package com.java2nb.novel.core.crawl;
import com.java2nb.novel.core.utils.RandomBookInfoUtil;
import com.java2nb.novel.core.utils.StringUtil;
import com.java2nb.novel.entity.Book;
import com.java2nb.novel.entity.BookContent;
import com.java2nb.novel.entity.BookIndex;
import com.java2nb.novel.utils.Constants;
import com.java2nb.novel.utils.CrawlHttpClient;
import io.github.xxyopen.util.IdWorker;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
*
* @author Administrator
*/
@Component
@RequiredArgsConstructor
public class CrawlParser {
private final IdWorker ID_WORKER = IdWorker.INSTANCE;
private final CrawlHttpClient crawlHttpClient;
@SneakyThrows
public void parseBook(RuleBean ruleBean, String bookId, CrawlBookHandler handler) {
Book book = new Book();
String bookDetailUrl = ruleBean.getBookDetailUrl().replace("{bookId}", bookId);
String bookDetailHtml = crawlHttpClient.get(bookDetailUrl, ruleBean.getCharset());
if (bookDetailHtml != null) {
Pattern bookNamePatten = PatternFactory.getPattern(ruleBean.getBookNamePatten());
Matcher bookNameMatch = bookNamePatten.matcher(bookDetailHtml);
boolean isFindBookName = bookNameMatch.find();
if (isFindBookName) {
String bookName = bookNameMatch.group(1);
//设置小说名
book.setBookName(bookName);
Pattern authorNamePatten = PatternFactory.getPattern(ruleBean.getAuthorNamePatten());
Matcher authorNameMatch = authorNamePatten.matcher(bookDetailHtml);
boolean isFindAuthorName = authorNameMatch.find();
if (isFindAuthorName) {
String authorName = authorNameMatch.group(1);
//设置作者名
book.setAuthorName(authorName);
if (StringUtils.isNotBlank(ruleBean.getPicUrlPatten())) {
Pattern picUrlPatten = PatternFactory.getPattern(ruleBean.getPicUrlPatten());
Matcher picUrlMatch = picUrlPatten.matcher(bookDetailHtml);
boolean isFindPicUrl = picUrlMatch.find();
if (isFindPicUrl) {
String picUrl = picUrlMatch.group(1);
if (StringUtils.isNotBlank(picUrl) && StringUtils.isNotBlank(ruleBean.getPicUrlPrefix())) {
picUrl = ruleBean.getPicUrlPrefix() + picUrl;
}
//设置封面图片路径
book.setPicUrl(picUrl);
}
}
if (StringUtils.isNotBlank(ruleBean.getScorePatten())) {
Pattern scorePatten = PatternFactory.getPattern(ruleBean.getScorePatten());
Matcher scoreMatch = scorePatten.matcher(bookDetailHtml);
boolean isFindScore = scoreMatch.find();
if (isFindScore) {
String score = scoreMatch.group(1);
//设置评分
book.setScore(Float.parseFloat(score));
}
}
if (StringUtils.isNotBlank(ruleBean.getVisitCountPatten())) {
Pattern visitCountPatten = PatternFactory.getPattern(ruleBean.getVisitCountPatten());
Matcher visitCountMatch = visitCountPatten.matcher(bookDetailHtml);
boolean isFindVisitCount = visitCountMatch.find();
if (isFindVisitCount) {
String visitCount = visitCountMatch.group(1);
//设置访问次数
book.setVisitCount(Long.parseLong(visitCount));
}
}
String desc = bookDetailHtml.substring(
bookDetailHtml.indexOf(ruleBean.getDescStart()) + ruleBean.getDescStart().length());
desc = desc.substring(0, desc.indexOf(ruleBean.getDescEnd()));
//过滤掉简介中的特殊标签
desc = desc.replaceAll("<a[^<]+</a>", "")
.replaceAll("<font[^<]+</font>", "")
.replaceAll("<p>\\s*</p>", "")
.replaceAll("<p>", "")
.replaceAll("</p>", "<br/>");
//设置书籍简介
book.setBookDesc(desc);
if (StringUtils.isNotBlank(ruleBean.getStatusPatten())) {
Pattern bookStatusPatten = PatternFactory.getPattern(ruleBean.getStatusPatten());
Matcher bookStatusMatch = bookStatusPatten.matcher(bookDetailHtml);
boolean isFindBookStatus = bookStatusMatch.find();
if (isFindBookStatus) {
String bookStatus = bookStatusMatch.group(1);
if (ruleBean.getBookStatusRule().get(bookStatus) != null) {
//设置更新状态
book.setBookStatus(ruleBean.getBookStatusRule().get(bookStatus));
}
}
}
if (StringUtils.isNotBlank(ruleBean.getUpadateTimePatten()) && StringUtils.isNotBlank(
ruleBean.getUpadateTimeFormatPatten())) {
Pattern updateTimePatten = PatternFactory.getPattern(ruleBean.getUpadateTimePatten());
Matcher updateTimeMatch = updateTimePatten.matcher(bookDetailHtml);
boolean isFindUpdateTime = updateTimeMatch.find();
if (isFindUpdateTime) {
String updateTime = updateTimeMatch.group(1);
//设置更新时间
book.setLastIndexUpdateTime(
new SimpleDateFormat(ruleBean.getUpadateTimeFormatPatten()).parse(updateTime));
}
}
}
if (book.getVisitCount() == null && book.getScore() != null) {
//随机根据评分生成访问次数
book.setVisitCount(RandomBookInfoUtil.getVisitCountByScore(book.getScore()));
} else if (book.getVisitCount() != null && book.getScore() == null) {
//随机根据访问次数生成评分
book.setScore(RandomBookInfoUtil.getScoreByVisitCount(book.getVisitCount()));
} else if (book.getVisitCount() == null && book.getScore() == null) {
//都没有,设置成固定值
book.setVisitCount(Constants.VISIT_COUNT_DEFAULT);
book.setScore(6.5f);
}
}
}
handler.handle(book);
}
public boolean parseBookIndexAndContent(String sourceBookId, Book book, RuleBean ruleBean,
Map<Integer, BookIndex> existBookIndexMap, CrawlBookChapterHandler handler) {
Date currentDate = new Date();
List<BookIndex> indexList = new ArrayList<>();
List<BookContent> contentList = new ArrayList<>();
//读取目录
String indexListUrl = ruleBean.getBookIndexUrl().replace("{bookId}", sourceBookId);
String indexListHtml = crawlHttpClient.get(indexListUrl, ruleBean.getCharset());
if (indexListHtml != null) {
if (StringUtils.isNotBlank(ruleBean.getBookIndexStart())) {
indexListHtml = indexListHtml.substring(
indexListHtml.indexOf(ruleBean.getBookIndexStart()) + ruleBean.getBookIndexStart().length());
}
Pattern indexIdPatten = PatternFactory.getPattern(ruleBean.getIndexIdPatten());
Matcher indexIdMatch = indexIdPatten.matcher(indexListHtml);
Pattern indexNamePatten = PatternFactory.getPattern(ruleBean.getIndexNamePatten());
Matcher indexNameMatch = indexNamePatten.matcher(indexListHtml);
boolean isFindIndex = indexIdMatch.find() & indexNameMatch.find();
int indexNum = 0;
//总字数
int totalWordCount = book.getWordCount() == null ? 0 : book.getWordCount();
while (isFindIndex) {
BookIndex hasIndex = existBookIndexMap.get(indexNum);
String indexName = indexNameMatch.group(1);
if (hasIndex == null || !StringUtils.deleteWhitespace(hasIndex.getIndexName())
.equals(StringUtils.deleteWhitespace(indexName))) {
String sourceIndexId = indexIdMatch.group(1);
String bookContentUrl = ruleBean.getBookContentUrl();
int calStart = bookContentUrl.indexOf("{cal_");
if (calStart != -1) {
//内容页URL需要进行计算才能得到
String calStr = bookContentUrl.substring(calStart,
calStart + bookContentUrl.substring(calStart).indexOf("}"));
String[] calArr = calStr.split("_");
int calType = Integer.parseInt(calArr[1]);
if (calType == 1) {
///{cal_1_1_3}_{bookId}/{indexId}.html
//第一种计算规则去除第x个参数的最后y个字母
int x = Integer.parseInt(calArr[2]);
int y = Integer.parseInt(calArr[3]);
String calResult;
if (x == 1) {
calResult = sourceBookId.substring(0, sourceBookId.length() - y);
} else {
calResult = sourceIndexId.substring(0, sourceBookId.length() - y);
}
if (calResult.length() == 0) {
calResult = "0";
}
bookContentUrl = bookContentUrl.replace(calStr + "}", calResult);
}
}
String contentUrl = bookContentUrl.replace("{bookId}", sourceBookId)
.replace("{indexId}", sourceIndexId);
//查询章节内容
String contentHtml = crawlHttpClient.get(contentUrl, ruleBean.getCharset());
if (contentHtml != null && !contentHtml.contains("正在手打中")) {
String content = contentHtml.substring(
contentHtml.indexOf(ruleBean.getContentStart()) + ruleBean.getContentStart().length());
content = content.substring(0, content.indexOf(ruleBean.getContentEnd()));
// 小说内容过滤
String filterContent = ruleBean.getFilterContent();
if (StringUtils.isNotBlank(filterContent)) {
String[] filterRules = filterContent.replace("\r\n", "\n").split("\n");
for (String filterRule : filterRules) {
if (StringUtils.isNotBlank(filterRule)) {
content = content.replaceAll(filterRule, "");
}
}
}
//插入章节目录和章节内容
BookIndex bookIndex = new BookIndex();
bookIndex.setIndexName(indexName);
bookIndex.setIndexNum(indexNum);
int wordCount = StringUtil.getStrValidWordCount(content);
bookIndex.setWordCount(wordCount);
indexList.add(bookIndex);
BookContent bookContent = new BookContent();
bookContent.setContent(content);
contentList.add(bookContent);
if (hasIndex != null) {
//章节更新
bookIndex.setId(hasIndex.getId());
bookContent.setIndexId(hasIndex.getId());
//计算总字数
totalWordCount = (totalWordCount + wordCount - hasIndex.getWordCount());
} else {
//章节插入
//设置目录和章节内容
Long indexId = ID_WORKER.nextId();
bookIndex.setId(indexId);
bookIndex.setBookId(book.getId());
bookIndex.setCreateTime(currentDate);
bookContent.setIndexId(indexId);
//计算总字数
totalWordCount += wordCount;
}
bookIndex.setUpdateTime(currentDate);
}
}
indexNum++;
isFindIndex = indexIdMatch.find() & indexNameMatch.find();
}
if (indexList.size() > 0) {
//如果有爬到最新章节,则设置小说主表的最新章节信息
//获取爬取到的最新章节
BookIndex lastIndex = indexList.get(indexList.size() - 1);
book.setLastIndexId(lastIndex.getId());
book.setLastIndexName(lastIndex.getIndexName());
book.setLastIndexUpdateTime(currentDate);
}
book.setWordCount(totalWordCount);
book.setUpdateTime(currentDate);
if (indexList.size() == contentList.size() && indexList.size() > 0) {
handler.handle(new ChapterBean() {{
setBookIndexList(indexList);
setBookContentList(contentList);
}});
return true;
}
}
handler.handle(new ChapterBean() {{
setBookIndexList(new ArrayList<>(0));
setBookContentList(new ArrayList<>(0));
}});
return false;
}
}

@ -0,0 +1,30 @@
package com.java2nb.novel.core.crawl;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
/**
* @author xiongxiaoyang
*/
public class PatternFactory {
private static final Map<String, Pattern> PATTERN_CACHED_MAP = new HashMap<>();
/**
* Pattern
*/
public static Pattern getPattern(String regex) {
Pattern pattern = PATTERN_CACHED_MAP.get(regex);
if (Objects.isNull(pattern)) {
pattern = Pattern.compile(regex);
PATTERN_CACHED_MAP.put(regex, pattern);
}
return pattern;
}
}

@ -0,0 +1,65 @@
package com.java2nb.novel.core.crawl;
import com.java2nb.novel.utils.Constants;
import lombok.Data;
import java.util.Map;
/**
* bean
*
* @author Administrator
*/
@Data
public class RuleBean {
/**
*
*/
private String charset = Constants.CRAWL_DEFAULT_CHARSET;
/**
* url
*/
private String updateBookListUrl;
/**
* URL
*/
private String bookListUrl;
private Map<String, String> catIdRule;
private Map<String, Byte> bookStatusRule;
private String bookIdPatten;
private String pagePatten;
private String totalPagePatten;
private String bookDetailUrl;
private String bookNamePatten;
private String authorNamePatten;
private String picUrlPatten;
private String statusPatten;
private String scorePatten;
private String visitCountPatten;
private String descStart;
private String descEnd;
private String upadateTimePatten;
private String upadateTimeFormatPatten;
private String bookIndexUrl;
private String indexIdPatten;
private String indexNamePatten;
private String bookContentUrl;
private String contentStart;
private String contentEnd;
private String picUrlPrefix;
private String bookIndexStart;
private String filterContent;
}

@ -0,0 +1,134 @@
package com.java2nb.novel.core.listener;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.java2nb.novel.core.crawl.CrawlParser;
import com.java2nb.novel.core.crawl.RuleBean;
import com.java2nb.novel.entity.Book;
import com.java2nb.novel.entity.BookIndex;
import com.java2nb.novel.entity.CrawlSingleTask;
import com.java2nb.novel.entity.CrawlSource;
import com.java2nb.novel.service.BookService;
import com.java2nb.novel.service.CrawlService;
import com.java2nb.novel.utils.Constants;
import jakarta.servlet.ServletContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author Administrator
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class StarterListener implements ServletContextInitializer {
private final BookService bookService;
private final CrawlService crawlService;
private final CrawlParser crawlParser;
@Value("${crawl.update.thread}")
private int updateThreadCount;
@Override
public void onStartup(ServletContext servletContext) {
for (int i = 0; i < updateThreadCount; i++) {
new Thread(() -> {
log.info("程序启动,开始执行自动更新线程。。。");
while (true) {
try {
//1.查询最新目录更新时间在一个月之内的前100条需要更新的数据
Date currentDate = new Date();
Date startDate = DateUtils.addDays(currentDate, -30);
List<Book> bookList;
synchronized (this) {
bookList = bookService.queryNeedUpdateBook(startDate, 100);
}
for (Book needUpdateBook : bookList) {
try {
//查询爬虫源规则
CrawlSource source = crawlService.queryCrawlSource(needUpdateBook.getCrawlSourceId());
RuleBean ruleBean = new ObjectMapper().readValue(source.getCrawlRule(), RuleBean.class);
//解析小说基本信息
crawlParser.parseBook(ruleBean, needUpdateBook.getCrawlBookId(), book -> {
//这里只做老书更新
book.setId(needUpdateBook.getId());
book.setWordCount(needUpdateBook.getWordCount());
if (needUpdateBook.getPicUrl() != null && needUpdateBook.getPicUrl()
.contains(Constants.LOCAL_PIC_PREFIX)) {
//本地图片则不更新
book.setPicUrl(null);
}
//查询已存在的章节
Map<Integer, BookIndex> existBookIndexMap = bookService.queryExistBookIndexMap(
needUpdateBook.getId());
//解析章节目录
crawlParser.parseBookIndexAndContent(needUpdateBook.getCrawlBookId(), book,
ruleBean, existBookIndexMap, chapter -> {
bookService.updateBookAndIndexAndContent(book, chapter.getBookIndexList(),
chapter.getBookContentList(), existBookIndexMap);
});
});
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
// 休眠10分钟
TimeUnit.MINUTES.sleep(10);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}).start();
}
new Thread(() -> {
log.info("程序启动,开始执行单本采集任务线程。。。");
while (true) {
CrawlSingleTask task = null;
byte crawlStatus = 0;
try {
//获取采集任务
task = crawlService.getCrawlSingleTask();
if (task != null) {
//查询爬虫规则
CrawlSource source = crawlService.queryCrawlSource(task.getSourceId());
RuleBean ruleBean = new ObjectMapper().readValue(source.getCrawlRule(), RuleBean.class);
if (crawlService.parseBookAndSave(task.getCatId(), ruleBean, task.getSourceId(),
task.getSourceBookId())) {
//采集成功
crawlStatus = 1;
}
}
//休眠1分钟
TimeUnit.MINUTES.sleep(1);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
if (task != null) {
crawlService.updateCrawlSingleTask(task, crawlStatus);
}
}
}).start();
}
}

@ -0,0 +1,61 @@
package com.java2nb.novel.core.schedule;
import com.java2nb.novel.core.cache.CacheKey;
import com.java2nb.novel.core.cache.CacheService;
import com.java2nb.novel.entity.CrawlSource;
import com.java2nb.novel.service.CrawlService;
import io.github.xxyopen.util.ThreadUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Set;
/**
* 线,
*
* @author Administrator
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class CrawlThreadMonitor {
private final CacheService cacheService;
private final CrawlService crawlService;
@Scheduled(fixedRate = 1000 * 60 * 5)
public void monitor() {
//查询需要监控的正在运行的爬虫源
List<CrawlSource> sources = crawlService.queryCrawlSourceByStatus((byte) 1);
for (CrawlSource source : sources) {
Set<Long> runningCrawlThreadIds = (Set<Long>) cacheService.getObject(CacheKey.RUNNING_CRAWL_THREAD_KEY_PREFIX + source.getId());
boolean sourceStop = true;
if (runningCrawlThreadIds != null) {
for (Long threadId : runningCrawlThreadIds) {
Thread thread = ThreadUtil.findThread(threadId);
if (thread != null && thread.isAlive()) {
//有活跃线程,说明该爬虫源正在运行,数据库中状态正确,不需要修改
sourceStop = false;
}
}
}
if (sourceStop) {
crawlService.updateCrawlSourceStatus(source.getId(), (byte) 0);
}
}
}
}

@ -0,0 +1,16 @@
package com.java2nb.novel.mapper;
import com.java2nb.novel.entity.BookIndex;
import org.apache.ibatis.annotations.Param;
/**
* @author Administrator
*/
public interface CrawlBookIndexMapper extends BookIndexMapper {
/**
*
* */
BookIndex queryLastIndex(@Param("bookId") Long bookId);
}

@ -0,0 +1,35 @@
package com.java2nb.novel.mapper;
import com.java2nb.novel.entity.Book;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
/**
* @author Administrator
*/
public interface CrawlBookMapper extends BookMapper {
/**
*
* @param startDate
* @param limit
* @return
* */
List<Book> queryNeedUpdateBook(@Param("startDate") Date startDate, @Param("limit") int limit);
/**
*
* @param bookId ID
* @return
* */
Integer queryTotalWordCount(@Param("bookId") Long bookId);
/**
*
* @param books
* @param currentDate
* */
void updateCrawlLastTime(@Param("books") List<Book> books,@Param("currentDate") Date currentDate);
}

@ -0,0 +1,16 @@
package com.java2nb.novel.service;
import com.java2nb.novel.entity.BookContent;
import java.util.List;
public interface BookContentService {
void saveBookContent(List<BookContent> bookContentList,Long bookId);
void saveBookContent(BookContent bookContent,Long bookId);
void updateBookContent(BookContent bookContent,Long bookId);
}

@ -0,0 +1,85 @@
package com.java2nb.novel.service;
import com.java2nb.novel.entity.Book;
import com.java2nb.novel.entity.BookContent;
import com.java2nb.novel.entity.BookIndex;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* @author Administrator
*/
public interface BookService {
/**
*
* @param bookName
* @param authorName
* @return
*/
boolean queryIsExistByBookNameAndAuthorName(String bookName, String authorName);
/**
*
* @param id ID
* @param sourceId ID
* @param bookId ID */
void updateCrawlProperties(Long id, Integer sourceId, String bookId);
/**
* ID
* @param catId ID
* @return
* */
String queryCatNameByCatId(int catId);
/**
*
* @param book
* @param bookIndexList
* @param bookContentList
* */
void saveBookAndIndexAndContent(Book book, List<BookIndex> bookIndexList, List<BookContent> bookContentList);
/**
*
*
* @param startDate
* @param limit
* @return
* */
List<Book> queryNeedUpdateBook(Date startDate, int limit);
/**
*
* @param bookId ID
* @return map
* */
Map<Integer,BookIndex> queryExistBookIndexMap(Long bookId);
/**
*
* @param book
* @param bookIndexList
* @param bookContentList
* @param existBookIndexMap Map */
void updateBookAndIndexAndContent(Book book, List<BookIndex> bookIndexList, List<BookContent> bookContentList, Map<Integer, BookIndex> existBookIndexMap);
/**
*
* @param bookId ID
* */
@Deprecated
void updateCrawlLastTime(Long bookId);
/**
*
* @param bookName
* @param authorName
* @return
* */
Book queryBookByBookNameAndAuthorName(String bookName, String authorName);
}

@ -0,0 +1,120 @@
package com.java2nb.novel.service;
import io.github.xxyopen.model.page.PageBean;
import com.java2nb.novel.core.crawl.RuleBean;
import com.java2nb.novel.entity.CrawlSingleTask;
import com.java2nb.novel.entity.CrawlSource;
import java.util.List;
/**
* @author Administrator
*/
public interface CrawlService {
/**
*
* @param source
* */
void addCrawlSource(CrawlSource source);
/**
*
* @param source
*/
void updateCrawlSource(CrawlSource source);
/**
*
* @param page
* @param pageSize
*@return
* */
PageBean<CrawlSource> listCrawlByPage(int page, int pageSize);
/**
*
* @param sourceId ID
* @param sourceStatus 01
* */
void openOrCloseCrawl(Integer sourceId, Byte sourceStatus);
/**
*
* @param sourceId ID
* @param sourceStatus 01
* */
void updateCrawlSourceStatus(Integer sourceId, Byte sourceStatus);
/**
*
* @param catId ID
* @param bookId ID
* @param sourceId ID
* @param ruleBean \
* @return true:false:
* */
boolean parseBookAndSave(int catId, RuleBean ruleBean, Integer sourceId, String bookId);
/**
*
* @param sourceStatus 01
* @return
* */
List<CrawlSource> queryCrawlSourceByStatus(Byte sourceStatus);
/**
* ID
* @param catId ID
* @param ruleBean
* @param sourceId ID
*/
void parseBookList(int catId, RuleBean ruleBean, Integer sourceId);
/**
*
* @param sourceId ID
* @return
* */
CrawlSource queryCrawlSource(Integer sourceId);
/**
*
* @param singleTask
* */
void addCrawlSingleTask(CrawlSingleTask singleTask);
/**
*
* @param page
* @param pageSize
* @return
* */
PageBean<CrawlSingleTask> listCrawlSingleTaskByPage(int page, int pageSize);
/**
*
* @param id ID
* */
void delCrawlSingleTask(Long id);
/**
*
* @return
* */
CrawlSingleTask getCrawlSingleTask();
/**
*
* @param task
* @param status
* */
void updateCrawlSingleTask(CrawlSingleTask task, Byte status);
/**
*
* @param id
* @return
*/
CrawlSource getCrawlSource(Integer id);
}

@ -0,0 +1,183 @@
package com.java2nb.novel.service.impl;
import com.java2nb.novel.entity.Book;
import com.java2nb.novel.entity.BookContent;
import com.java2nb.novel.entity.BookIndex;
import com.java2nb.novel.mapper.*;
import com.java2nb.novel.service.BookContentService;
import com.java2nb.novel.service.BookService;
import com.java2nb.novel.utils.Constants;
import lombok.RequiredArgsConstructor;
import org.mybatis.dynamic.sql.render.RenderingStrategies;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.java2nb.novel.mapper.BookDynamicSqlSupport.crawlBookId;
import static com.java2nb.novel.mapper.BookDynamicSqlSupport.crawlSourceId;
import static com.java2nb.novel.mapper.CrawlSourceDynamicSqlSupport.id;
import static org.mybatis.dynamic.sql.SqlBuilder.*;
import static org.mybatis.dynamic.sql.select.SelectDSL.select;
/**
* @author Administrator
*/
@Service
@RequiredArgsConstructor
public class BookServiceImpl implements BookService {
private final CrawlBookMapper bookMapper;
private final BookCategoryMapper bookCategoryMapper;
private final CrawlBookIndexMapper bookIndexMapper;
private final Map<String, BookContentService> bookContentServiceMap;
@Value("${content.save.storage}")
private String storageType;
@Override
public boolean queryIsExistByBookNameAndAuthorName(String bookName, String authorName) {
return bookMapper.count(countFrom(BookDynamicSqlSupport.book).where(BookDynamicSqlSupport.bookName, isEqualTo(bookName))
.and(BookDynamicSqlSupport.authorName, isEqualTo(authorName))
.build()
.render(RenderingStrategies.MYBATIS3)) > 0;
}
@Override
public void updateCrawlProperties(Long id, Integer sourceId, String bookId) {
bookMapper.update(update(BookDynamicSqlSupport.book)
.set(crawlSourceId)
.equalTo(sourceId)
.set(crawlBookId)
.equalTo(bookId)
.where(BookDynamicSqlSupport.id, isEqualTo(id))
.build()
.render(RenderingStrategies.MYBATIS3));
}
@Override
public String queryCatNameByCatId(int catId) {
return bookCategoryMapper.selectMany(select(BookCategoryDynamicSqlSupport.name)
.from(BookCategoryDynamicSqlSupport.bookCategory)
.where(id, isEqualTo(catId))
.build()
.render(RenderingStrategies.MYBATIS3)).get(0).getName();
}
@Transactional(rollbackFor = Exception.class)
@Override
public void saveBookAndIndexAndContent(Book book, List<BookIndex> bookIndexList, List<BookContent> bookContentList) {
if (!queryIsExistByBookNameAndAuthorName(book.getBookName(), book.getAuthorName())) {
if (bookIndexList.size() > 0) {
//保存小说主表
book.setCreateTime(new Date());
bookMapper.insertSelective(book);
//批量保存目录和内容
bookIndexList.forEach(bookIndex -> {
bookIndex.setStorageType(storageType);
});
bookIndexMapper.insertMultiple(bookIndexList);
bookContentServiceMap.get(storageType).saveBookContent(bookContentList, book.getId());
}
}
}
@Override
public List<Book> queryNeedUpdateBook(Date startDate, int limit) {
List<Book> books = bookMapper.queryNeedUpdateBook(startDate, limit);
if (books.size() > 0) {
//更新最后抓取时间为当前时间
bookMapper.updateCrawlLastTime(books, new Date());
}
return books;
}
@Override
public Map<Integer, BookIndex> queryExistBookIndexMap(Long bookId) {
List<BookIndex> bookIndexs = bookIndexMapper.selectMany(select(BookIndexDynamicSqlSupport.id, BookIndexDynamicSqlSupport.indexNum, BookIndexDynamicSqlSupport.indexName, BookIndexDynamicSqlSupport.wordCount, BookIndexDynamicSqlSupport.storageType)
.from(BookIndexDynamicSqlSupport.bookIndex)
.where(BookIndexDynamicSqlSupport.bookId, isEqualTo(bookId))
.build()
.render(RenderingStrategies.MYBATIS3));
if (bookIndexs.size() > 0) {
return bookIndexs.stream().collect(Collectors.toMap(BookIndex::getIndexNum, Function.identity()));
}
return new HashMap<>(0);
}
@Transactional(rollbackFor = Exception.class)
@Override
public void updateBookAndIndexAndContent(Book book, List<BookIndex> bookIndexList, List<BookContent> bookContentList, Map<Integer, BookIndex> existBookIndexMap) {
for (int i = 0; i < bookIndexList.size(); i++) {
BookIndex bookIndex = bookIndexList.get(i);
BookContent bookContent = bookContentList.get(i);
if (!existBookIndexMap.containsKey(bookIndex.getIndexNum())) {
//插入
bookIndex.setStorageType(storageType);
bookIndexMapper.insertSelective(bookIndex);
bookContentServiceMap.get(storageType).saveBookContent(bookContent, book.getId());
} else {
//更新
bookIndexMapper.updateByPrimaryKeySelective(bookIndex);
bookContentServiceMap.get(existBookIndexMap.get(bookIndex.getIndexNum()).getStorageType()).updateBookContent(bookContent, book.getId());
}
}
//更新小说主表
book.setBookName(null);
book.setAuthorName(null);
if (Constants.VISIT_COUNT_DEFAULT.equals(book.getVisitCount())) {
book.setVisitCount(null);
}
bookMapper.updateByPrimaryKeySelective(book);
}
@Override
public void updateCrawlLastTime(Long bookId) {
Book book = new Book();
book.setId(bookId);
book.setCrawlLastTime(new Date());
bookMapper.updateByPrimaryKeySelective(book);
}
@Override
public Book queryBookByBookNameAndAuthorName(String bookName, String authorName) {
List<Book> books = bookMapper.selectMany(select(BookDynamicSqlSupport.id).from(BookDynamicSqlSupport.book)
.where(BookDynamicSqlSupport.bookName, isEqualTo(bookName))
.and(BookDynamicSqlSupport.authorName, isEqualTo(authorName))
.build()
.render(RenderingStrategies.MYBATIS3));
if (books.size() > 0) {
return books.get(0);
}
return null;
}
}

@ -0,0 +1,388 @@
package com.java2nb.novel.service.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.PageHelper;
import com.java2nb.novel.core.cache.CacheKey;
import com.java2nb.novel.core.cache.CacheService;
import com.java2nb.novel.core.crawl.CrawlParser;
import com.java2nb.novel.core.crawl.RuleBean;
import com.java2nb.novel.core.enums.ResponseStatus;
import com.java2nb.novel.core.utils.SpringUtil;
import com.java2nb.novel.entity.Book;
import com.java2nb.novel.entity.CrawlSingleTask;
import com.java2nb.novel.entity.CrawlSource;
import com.java2nb.novel.mapper.CrawlSingleTaskDynamicSqlSupport;
import com.java2nb.novel.mapper.CrawlSingleTaskMapper;
import com.java2nb.novel.mapper.CrawlSourceDynamicSqlSupport;
import com.java2nb.novel.mapper.CrawlSourceMapper;
import com.java2nb.novel.service.BookService;
import com.java2nb.novel.service.CrawlService;
import com.java2nb.novel.utils.CrawlHttpClient;
import com.java2nb.novel.vo.CrawlSingleTaskVO;
import com.java2nb.novel.vo.CrawlSourceVO;
import io.github.xxyopen.model.page.PageBean;
import io.github.xxyopen.model.page.builder.pagehelper.PageBuilder;
import io.github.xxyopen.util.IdWorker;
import io.github.xxyopen.util.ThreadUtil;
import io.github.xxyopen.web.exception.BusinessException;
import io.github.xxyopen.web.util.BeanUtil;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.dynamic.sql.render.RenderingStrategies;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.java2nb.novel.mapper.CrawlSourceDynamicSqlSupport.*;
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo;
import static org.mybatis.dynamic.sql.select.SelectDSL.select;
/**
* @author Administrator
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class CrawlServiceImpl implements CrawlService {
private final CrawlParser crawlParser;
private final CrawlSourceMapper crawlSourceMapper;
private final CrawlSingleTaskMapper crawlSingleTaskMapper;
private final BookService bookService;
private final CacheService cacheService;
private final IdWorker idWorker = IdWorker.INSTANCE;
private final CrawlHttpClient crawlHttpClient;
@Override
public void addCrawlSource(CrawlSource source) {
Date currentDate = new Date();
source.setCreateTime(currentDate);
source.setUpdateTime(currentDate);
crawlSourceMapper.insertSelective(source);
}
@Override
public void updateCrawlSource(CrawlSource source) {
if (source.getId() != null) {
Optional<CrawlSource> opt = crawlSourceMapper.selectByPrimaryKey(source.getId());
if (opt.isPresent()) {
CrawlSource crawlSource = opt.get();
if (crawlSource.getSourceStatus() == (byte) 1) {
//关闭
openOrCloseCrawl(crawlSource.getId(), (byte) 0);
}
Date currentDate = new Date();
crawlSource.setUpdateTime(currentDate);
crawlSource.setCrawlRule(source.getCrawlRule());
crawlSource.setSourceName(source.getSourceName());
crawlSourceMapper.updateByPrimaryKey(crawlSource);
}
}
}
@Override
public PageBean<CrawlSource> listCrawlByPage(int page, int pageSize) {
PageHelper.startPage(page, pageSize);
SelectStatementProvider render = select(id, sourceName, sourceStatus, createTime, updateTime)
.from(crawlSource)
.orderBy(updateTime)
.build()
.render(RenderingStrategies.MYBATIS3);
List<CrawlSource> crawlSources = crawlSourceMapper.selectMany(render);
PageBean<CrawlSource> pageBean = PageBuilder.build(crawlSources);
pageBean.setList(BeanUtil.copyList(crawlSources, CrawlSourceVO.class));
return pageBean;
}
@SneakyThrows
@Override
public void openOrCloseCrawl(Integer sourceId, Byte sourceStatus) {
//判断是开启还是关闭,如果是关闭,则修改数据库状态后获取该爬虫正在运行的线程集合并全部停止
//如果是开启,先查询数据库中状态,判断该爬虫源是否还在运行,如果在运行,则忽略,
// 如果没有则修改数据库状态并启动线程爬取小说数据加入到runningCrawlThread中
if (sourceStatus == (byte) 0) {
//关闭,直接修改数据库状态,并直接修改数据库状态后获取该爬虫正在运行的线程集合全部停止
SpringUtil.getBean(CrawlService.class).updateCrawlSourceStatus(sourceId, sourceStatus);
Set<Long> runningCrawlThreadId = (Set<Long>) cacheService.getObject(
CacheKey.RUNNING_CRAWL_THREAD_KEY_PREFIX + sourceId);
if (runningCrawlThreadId != null) {
for (Long ThreadId : runningCrawlThreadId) {
Thread thread = ThreadUtil.findThread(ThreadId);
if (thread != null && thread.isAlive()) {
thread.interrupt();
}
}
}
} else {
//开启
//查询爬虫源状态和规则
CrawlSource source = queryCrawlSource(sourceId);
Byte realSourceStatus = source.getSourceStatus();
if (realSourceStatus == (byte) 0) {
//该爬虫源已经停止运行了,修改数据库状态并启动线程爬取小说数据加入到runningCrawlThread中
SpringUtil.getBean(CrawlService.class).updateCrawlSourceStatus(sourceId, sourceStatus);
RuleBean ruleBean = new ObjectMapper().readValue(source.getCrawlRule(), RuleBean.class);
Set<Long> threadIds = new HashSet<>();
//按分类开始爬虫解析任务
for (int i = 1; i < 8; i++) {
final int catId = i;
Thread thread = new Thread(() -> CrawlServiceImpl.this.parseBookList(catId, ruleBean, sourceId));
thread.start();
//thread加入到监控缓存中
threadIds.add(thread.getId());
}
cacheService.setObject(CacheKey.RUNNING_CRAWL_THREAD_KEY_PREFIX + sourceId, threadIds);
}
}
}
@Override
public CrawlSource queryCrawlSource(Integer sourceId) {
SelectStatementProvider render = select(CrawlSourceDynamicSqlSupport.sourceStatus,
CrawlSourceDynamicSqlSupport.crawlRule)
.from(crawlSource)
.where(id, isEqualTo(sourceId))
.build()
.render(RenderingStrategies.MYBATIS3);
return crawlSourceMapper.selectMany(render).get(0);
}
@Override
public void addCrawlSingleTask(CrawlSingleTask singleTask) {
if (bookService.queryIsExistByBookNameAndAuthorName(singleTask.getBookName(), singleTask.getAuthorName())) {
throw new BusinessException(ResponseStatus.BOOK_EXISTS);
}
singleTask.setCreateTime(new Date());
crawlSingleTaskMapper.insertSelective(singleTask);
}
@Override
public PageBean<CrawlSingleTask> listCrawlSingleTaskByPage(int page, int pageSize) {
PageHelper.startPage(page, pageSize);
SelectStatementProvider render = select(CrawlSingleTaskDynamicSqlSupport.crawlSingleTask.allColumns())
.from(CrawlSingleTaskDynamicSqlSupport.crawlSingleTask)
.orderBy(CrawlSingleTaskDynamicSqlSupport.createTime.descending())
.build()
.render(RenderingStrategies.MYBATIS3);
List<CrawlSingleTask> crawlSingleTasks = crawlSingleTaskMapper.selectMany(render);
PageBean<CrawlSingleTask> pageBean = PageBuilder.build(crawlSingleTasks);
pageBean.setList(BeanUtil.copyList(crawlSingleTasks, CrawlSingleTaskVO.class));
return pageBean;
}
@Override
public void delCrawlSingleTask(Long id) {
crawlSingleTaskMapper.deleteByPrimaryKey(id);
}
@Override
public CrawlSingleTask getCrawlSingleTask() {
List<CrawlSingleTask> list = crawlSingleTaskMapper.selectMany(
select(CrawlSingleTaskDynamicSqlSupport.crawlSingleTask.allColumns())
.from(CrawlSingleTaskDynamicSqlSupport.crawlSingleTask)
.where(CrawlSingleTaskDynamicSqlSupport.taskStatus, isEqualTo((byte) 2))
.orderBy(CrawlSingleTaskDynamicSqlSupport.createTime)
.limit(1)
.build()
.render(RenderingStrategies.MYBATIS3));
return list.size() > 0 ? list.get(0) : null;
}
@Override
public void updateCrawlSingleTask(CrawlSingleTask task, Byte status) {
byte excCount = task.getExcCount();
excCount += 1;
task.setExcCount(excCount);
if (status == 1 || excCount == 5) {
//当采集成功或者采集次数等于5则更新采集最终状态并停止采集
task.setTaskStatus(status);
}
crawlSingleTaskMapper.updateByPrimaryKeySelective(task);
}
@Override
public CrawlSource getCrawlSource(Integer id) {
Optional<CrawlSource> opt = crawlSourceMapper.selectByPrimaryKey(id);
if (opt.isPresent()) {
CrawlSource crawlSource = opt.get();
return crawlSource;
}
return null;
}
/**
*
*/
@Override
public void parseBookList(int catId, RuleBean ruleBean, Integer sourceId) {
//当前页码1
int page = 1;
int totalPage = page;
while (page <= totalPage) {
try {
String catIdRule = ruleBean.getCatIdRule().get("catId" + catId);
if (StringUtils.isNotBlank(catIdRule)) {
String catBookListUrl = "";
if (StringUtils.isNotBlank(ruleBean.getBookListUrl())) {
// 兼容老规则
// 拼接分类URL
catBookListUrl = ruleBean.getBookListUrl()
.replace("{catId}", catIdRule)
.replace("{page}", page + "");
} else {
// 新规则
// 拼接分类URL
catBookListUrl = catIdRule.replace("{page}", page + "");
}
log.info("catBookListUrl{}", catBookListUrl);
String bookListHtml = crawlHttpClient.get(catBookListUrl, ruleBean.getCharset());
if (bookListHtml != null) {
Pattern bookIdPatten = Pattern.compile(ruleBean.getBookIdPatten());
Matcher bookIdMatcher = bookIdPatten.matcher(bookListHtml);
boolean isFindBookId = bookIdMatcher.find();
while (isFindBookId) {
try {
//1.阻塞过程(使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时)
//捕获中断异常InterruptedException来退出线程。
//2.非阻塞过程中通过判断中断标志来退出线程。
if (Thread.currentThread().isInterrupted()) {
return;
}
String bookId = bookIdMatcher.group(1);
parseBookAndSave(catId, ruleBean, sourceId, bookId);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
isFindBookId = bookIdMatcher.find();
}
Pattern totalPagePatten = Pattern.compile(ruleBean.getTotalPagePatten());
Matcher totalPageMatcher = totalPagePatten.matcher(bookListHtml);
boolean isFindTotalPage = totalPageMatcher.find();
if (isFindTotalPage) {
totalPage = Integer.parseInt(totalPageMatcher.group(1));
}
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
if (page == totalPage) {
// 第一遍采集完成,翻到第一页,继续第二次采集,适用于分页数比较少的最近更新列表
page = 0;
}
page += 1;
}
}
@Override
public boolean parseBookAndSave(int catId, RuleBean ruleBean, Integer sourceId, String bookId) {
final AtomicBoolean parseResult = new AtomicBoolean(false);
crawlParser.parseBook(ruleBean, bookId, book -> {
if (book.getBookName() == null || book.getAuthorName() == null) {
return;
}
//这里只做新书入库,查询是否存在这本书
Book existBook = bookService.queryBookByBookNameAndAuthorName(book.getBookName(), book.getAuthorName());
//如果该小说不存在则可以解析入库但是标记该小说正在入库30分钟之后才允许再次入库
if (existBook == null) {
//没有该书,可以入库
book.setCatId(catId);
//根据分类ID查询分类
book.setCatName(bookService.queryCatNameByCatId(catId));
if (catId == 7) {
//女频
book.setWorkDirection((byte) 1);
} else {
//男频
book.setWorkDirection((byte) 0);
}
book.setCrawlBookId(bookId);
book.setCrawlSourceId(sourceId);
book.setCrawlLastTime(new Date());
book.setId(idWorker.nextId());
//解析章节目录
boolean parseIndexContentResult = crawlParser.parseBookIndexAndContent(bookId, book, ruleBean,
new HashMap<>(0), chapter -> {
bookService.saveBookAndIndexAndContent(book, chapter.getBookIndexList(),
chapter.getBookContentList());
});
parseResult.set(parseIndexContentResult);
} else {
//只更新书籍的爬虫相关字段
bookService.updateCrawlProperties(existBook.getId(), sourceId, bookId);
parseResult.set(true);
}
});
return parseResult.get();
}
@Override
public void updateCrawlSourceStatus(Integer sourceId, Byte sourceStatus) {
CrawlSource source = new CrawlSource();
source.setId(sourceId);
source.setSourceStatus(sourceStatus);
crawlSourceMapper.updateByPrimaryKeySelective(source);
}
@Override
public List<CrawlSource> queryCrawlSourceByStatus(Byte sourceStatus) {
SelectStatementProvider render = select(CrawlSourceDynamicSqlSupport.id,
CrawlSourceDynamicSqlSupport.sourceStatus, CrawlSourceDynamicSqlSupport.crawlRule)
.from(crawlSource)
.where(CrawlSourceDynamicSqlSupport.sourceStatus, isEqualTo(sourceStatus))
.build()
.render(RenderingStrategies.MYBATIS3);
return crawlSourceMapper.selectMany(render);
}
}

@ -0,0 +1,43 @@
package com.java2nb.novel.service.impl;
import com.java2nb.novel.entity.BookContent;
import com.java2nb.novel.mapper.BookContentDynamicSqlSupport;
import com.java2nb.novel.mapper.BookContentMapper;
import com.java2nb.novel.service.BookContentService;
import lombok.RequiredArgsConstructor;
import org.mybatis.dynamic.sql.render.RenderingStrategies;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import java.util.List;
import static org.mybatis.dynamic.sql.SqlBuilder.isEqualTo;
import static org.mybatis.dynamic.sql.SqlBuilder.update;
@Service(value = "db")
@RequiredArgsConstructor
public class DbBookContentServiceImpl implements BookContentService {
private final BookContentMapper bookContentMapper;
@Override
public void saveBookContent(List<BookContent> bookContentList,Long bookId) {
bookContentMapper.insertMultiple(bookContentList);
}
@Override
public void saveBookContent(BookContent bookContent,Long bookId) {
bookContentMapper.insertSelective(bookContent);
}
@Override
public void updateBookContent(BookContent bookContent,Long bookId) {
bookContentMapper.update(update(BookContentDynamicSqlSupport.bookContent)
.set(BookContentDynamicSqlSupport.content)
.equalTo(bookContent.getContent())
.where(BookContentDynamicSqlSupport.indexId,isEqualTo(bookContent.getIndexId()))
.build()
.render(RenderingStrategies.MYBATIS3));
}
}

@ -0,0 +1,35 @@
package com.java2nb.novel.service.impl;
import com.java2nb.novel.core.utils.FileUtil;
import com.java2nb.novel.entity.BookContent;
import com.java2nb.novel.service.BookContentService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import java.util.List;
@Service(value = "txt")
@RequiredArgsConstructor
public class FileBookContentServiceImpl implements BookContentService {
@Value("${content.save.path}")
private String fileSavePath;
@Override
public void saveBookContent(List<BookContent> bookContentList,Long bookId) {
bookContentList.forEach(bookContent -> saveBookContent(bookContent,bookId));
}
@Override
public void saveBookContent(BookContent bookContent,Long bookId) {
FileUtil.writeContentToFile(fileSavePath,"/"+bookId+"/"+bookContent.getIndexId()+".txt",bookContent.getContent());
}
@Override
public void updateBookContent(BookContent bookContent,Long bookId) {
FileUtil.writeContentToFile(fileSavePath,"/"+bookId+"/"+bookContent.getIndexId()+".txt",bookContent.getContent());
}
}

@ -0,0 +1,32 @@
package com.java2nb.novel.utils;
/**
* @author Administrator
*/
public class Constants {
/**
*
*/
public static final String LOCAL_PIC_PREFIX = "/localPic/";
/**
* 访
*/
public static final Long VISIT_COUNT_DEFAULT = 100L;
/**
* http
*/
public static final int INVALID_HTML_LENGTH = 1500;
/**
* http
*/
public static final Integer HTTP_FAIL_RETRY_COUNT = 3;
/**
*
*/
public static final String CRAWL_DEFAULT_CHARSET = "UTF-8";
}

@ -0,0 +1,57 @@
package com.java2nb.novel.utils;
import com.java2nb.novel.core.utils.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Objects;
import java.util.Random;
/**
* @author Administrator
*/
@Slf4j
@Component
public class CrawlHttpClient {
@Value("${crawl.interval.min}")
private Integer intervalMin;
@Value("${crawl.interval.max}")
private Integer intervalMax;
private final Random random = new Random();
private static final ThreadLocal<Integer> RETRY_COUNT = new ThreadLocal<>();
public String get(String url, String charset) {
if (Objects.nonNull(intervalMin) && Objects.nonNull(intervalMax) && intervalMax > intervalMin) {
try {
Thread.sleep(random.nextInt(intervalMax - intervalMin + 1) + intervalMin);
} catch (InterruptedException e) {
log.error(e.getMessage(), e);
}
}
String body = HttpUtil.getByHttpClientWithChrome(url, charset);
if (Objects.isNull(body) || body.length() < Constants.INVALID_HTML_LENGTH) {
return processErrorHttpResult(url, charset);
}
//成功获得html内容
return body;
}
private String processErrorHttpResult(String url, String charset) {
Integer count = RETRY_COUNT.get();
if (count == null) {
count = 0;
}
if (count < Constants.HTTP_FAIL_RETRY_COUNT) {
RETRY_COUNT.set(++count);
return get(url, charset);
}
RETRY_COUNT.remove();
return null;
}
}

@ -0,0 +1,27 @@
package com.java2nb.novel.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.java2nb.novel.entity.CrawlSingleTask;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* @author Administrator
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class CrawlSingleTaskVO extends CrawlSingleTask {
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm")
private Date createTime;
@Override
public String toString() {
return super.toString();
}
}

@ -0,0 +1,29 @@
package com.java2nb.novel.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.java2nb.novel.entity.CrawlSource;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* @author Administrator
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class CrawlSourceVO extends CrawlSource{
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm")
private Date createTime;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm")
private Date updateTime;
@Override
public String toString() {
return super.toString();
}
}

@ -0,0 +1,3 @@
spring:
config:
import: classpath:application-common-dev.yml

@ -0,0 +1,3 @@
spring:
config:
import: classpath:application-common-prod.yml

@ -0,0 +1,3 @@
spring:
profiles:
include: [common-test]

@ -0,0 +1,33 @@
#
server:
port: 8081
servlet:
session:
timeout: 1D
spring:
profiles:
active: dev
#
admin:
username: admin
password: admin
crawl:
update:
#线
#1
#CPU线
thread: 1
#
interval:
min: 300
max: 500

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}" />
<!-- %m,%p,%t线,%d,%c,%i0,,, -->
<!-- appenderconfiguration -->
<!-- ConsoleAppender -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<!-- 使UTF-8使GBK -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- RollingFileAppender -->
<!-- 1.XXX%%novel-crawl.log -->
<!-- 2.10MB -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- -->
<file>logs/novel-crawl.log</file>
<!-- rollingPolicy: RollingFileAppender -->
<!-- SizeAndTimeBasedRollingPolicy -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- fileNamePattern -->
<!-- logs/debug.2023-10-01.0.log -->
<fileNamePattern>logs/debug.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- 30 -->
<maxHistory>30</maxHistory>
<!-- -->
<maxFileSize>10MB</maxFileSize>
<!-- -->
<totalSizeCap>1GB</totalSizeCap>
</rollingPolicy>
<encoder>
<!-- pattern -->
<pattern>%d %p (%file:%line\)- %m%n</pattern>
<!-- : - -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- -->
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
<!-- -->
<!-- com.java2nb DEBUG -->
<!-- FATAL > ERROR > WARN > INFO > DEBUG > TRACE -->
<logger name="com.java2nb" level="DEBUG" additivity="false">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</logger>
</configuration>

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.java2nb.novel.mapper.CrawlBookIndexMapper">
<select id="queryLastIndex" parameterType="long" resultType="com.java2nb.novel.entity.BookIndex">
select id,index_name,update_time
from book_index where book_id = #{bookId}
order by index_num DESC limit 1
</select>
</mapper>

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.java2nb.novel.mapper.CrawlBookMapper">
<select id="queryNeedUpdateBook" resultType="com.java2nb.novel.entity.Book">
select id,crawl_source_id,crawl_book_id,crawl_last_time,pic_url,word_count
from book where last_index_update_time > #{startDate} and crawl_source_id is not null
order by crawl_last_time
limit ${limit}
</select>
<select id="queryTotalWordCount" parameterType="long" resultType="int">
select sum(word_count) from book_index where book_id = #{bookId}
</select>
<update id="updateCrawlLastTime">
update book set crawl_last_time = #{currentDate}
where id in
<foreach item="book" collection="books" open="(" separator="," close=")">
#{book.id}
</foreach>
</update>
</mapper>

@ -0,0 +1,967 @@
@charset "utf-8";
body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, fieldset, input, p, a, blockquote, th {
margin: 0;
padding: 0
}
h1, h2, h3, h4, h5, h6 {
font-size: 14px
}
ol, ul, li {
list-style: none outside none
}
table {
border-collapse: collapsse;
border-spacing: 0
}
fieldset, img {
border: 0 none
}
/*html { background: ##f5f5f5 }*/
body {
background: #f5f5f5;
color: #333;
font: 12px/1.5 PingFangSC-Regular, HelveticaNeue-Light, 'Helvetica Neue Light', 'Microsoft YaHei', sans-serif, "宋体";
text-align: left
}
input::-moz-focus-inner {
border: none;
padding: 0
}
a img {
border: none
}
a {
outline: none;
color: #333;
text-decoration: none
}
a:hover, .topBar a:hover, .red, .record_list li:hover .read_link a {
color: #3eaf7c
}
.red1 {
color: #ff4040
}
.unlink {
text-decoration: underline
}
.blue {
color: #5fc3f3
}
.green {
color: #360
}
.black {
color: #000
}
.black3 {
color: #333
}
.black6 {
color: #666
}
.black9 {
color: #999
}
.ccc {
color: #ccc
}
.orange {
color: #f60
}
.font12 {
font-size: 12px !important
}
.font14 {
font-size: 14px !important
}
.font16 {
font-size: 16px !important
}
.font18 {
font-size: 18px !important
}
.font20 {
font-size: 20px !important
}
.font26 {
font-size: 26px !important
}
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-break: keep-all
}
textarea {
resize: none;
outline: none;
border: 1px solid #CCC;
font: 12px/1.8 "microsoft yahei", Arial;
padding-left: 5px
}
input {
outline: none;
border: none; /* padding-left: 5px; font-size: 13px;*/
font-family: "microsoft yahei", Arial;
*background: none
}
i, em, cite {
font-style: normal
}
.layui-inline, input, label {
vertical-align: middle
}
button, input, optgroup, select, textarea {
color: inherit;
font: inherit;
margin: 0;
outline: 0
}
button, select {
text-transform: none
}
/*select { -webkit-appearance: none; border: none }*/
input {
line-height: normal
}
input[type=checkbox], input[type=radio] {
box-sizing: border-box;
padding: 0
}
input[type=number]::-webkit-inner-spin-button, input[type=number]::-webkit-outer-spin-button {
height: auto
}
input[type=search] {
-webkit-appearance: textfield;
-moz-box-sizing: content-box;
-webkit-box-sizing: content-box;
box-sizing: content-box
}
input[type=search]::-webkit-search-cancel-button, input[type=search]::-webkit-search-decoration {
-webkit-appearance: none
}
input[type="submit"], input[type="reset"], input[type="button"], button {
-webkit-appearance: none
}
:-moz-placeholder {
color: #999
}
::-moz-placeholder {
color: #999
}
input:-ms-input-placeholder, textarea:-ms-input-placeholder {
color: #999
}
input::-webkit-input-placeholder, textarea::-webkit-input-placeholder {
color: #999
}
.cf {
zoom: 1
}
.cf:before, .cf:after {
content: "";
display: table;
display: block;
font-size: 0;
height: 0;
line-height: 0;
clear: both;
visibility: hidden
}
.cf:after {
clear: both
}
.clear {
clear: both
}
.tl {
text-align: left
}
.tc {
text-align: center
}
.tr {
text-align: right
}
.fl {
float: left
}
.fr {
float: right
}
.block {
display: block
}
.none, .hidden {
display: none
}
/*base end*/
.channelWrap {
background: #fff;
border-radius: 6px;
padding: 20px;
margin-bottom: 20px
}
.channelWrap.channelBanner {
padding-bottom: 14px
}
.wrap_left {
width: 750px
}
.wrap_right {
width: 250px
}
.wrap_inner {
padding: 20px;
border-radius: 6px;
background: #fff;
}
.wrap_bg {
border-radius: 6px;
background: #fff;
}
.pad20 {
padding: 20px
}
.pad20_nobt {
padding: 20px 20px 0
}
.topBar {
width: 100%;
background: #fbfaf8;
border-bottom: 1px solid #eae6e2;
height: 35px;
line-height: 35px
}
.box_center {
width: 1020px;
margin: 0 auto
}
.top_l {
float: left
}
.top_r {
float: right
}
.topBar .line {
display: inline-block;
padding: 0 12px;
color: #e5d9da
}
.topBar a {
display: inline-block;
color: #8C8C8C
}
.topBar a.on {
color: #333
}
.topMain {
height: 92px;
background: #fff;
overflow: hidden
}
.logo {
width: 160px;
float: left;
padding: 23px 130px 0 0;
display: block
}
.logo img {
width: auto;
height: 48px
}
.searchBar {
width: 342px;
margin-top: 27px;
overflow: hidden
}
.searchBar .search /*, .searchBar .hotword*/
{
width: 342px;
overflow: hidden
}
.searchBar .s_int {
width: 250px;
padding: 0 14px 0 18px;
height: 36px;
line-height: 36px \9;
vertical-align: middle;
border: 1px solid #3eaf7c;
border-right: none;
color: #333;
float: left;
border-radius: 20px 0 0 20px;
font-size: 14px; /*background: #fff;*/
background: 0 0
}
/*.searchBar .s_btn { width: 78px; height: 38px; line-height: 38px; background: #f65167; color: #fff; font-size: 16px; text-align: center; float: left; cursor: pointer; padding: 0 }
.searchBar .s_btn:hover { background:#E23249 }*/
.searchBar .search_btn {
float: left;
width: 58px;
height: 38px;
text-align: center;
border-radius: 0 20px 20px 0;
background-color: #3eaf7c;
cursor: pointer;
}
.searchBar .search_btn .icon {
width: 18px;
height: 18px;
display: block;
margin: 9px auto 0;
background: url(../images/search.png) no-repeat;
background-size: cover;
filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='/images/search.png', sizingMethod='scale');
}
/*.hotword { padding-top: 3px }
.hotword a, .hotword span { color: #999; margin: 0 6px 0 5px }
.hotword a:hover { color: #666 }*/
.bookShelf {
margin-top: 27px;
padding-left: 20px;
overflow: hidden
}
.bookShelf .sj_link {
height: 38px;
line-height: 38px;
padding-left: 30px;
font-size: 15px;
color: #404040;
background: url(../images/icon_sj.png) no-repeat 6px 50%;
float: left
}
.bookShelf .user_link {
height: 38px;
line-height: 38px;
padding-left: 20px;
font-size: 15px;
color: #404040;
float: right
}
.bookShelf .user_head {
width: 26px;
height: 26px;
border-radius: 50%;
float: left;
margin: 6px 5px 0 0
}
.bookShelf .user_name {
max-width: 100px;
display: inline-block
}
.bookShelf .line {
float: left;
color: #ccc
}
/*.bookShelf img { position: absolute; top: 17px; left: 17px; z-index: 10 }*/
.mainNav {
width: 100%;
height: 48px;
background: #3eaf7c;
margin-bottom: 20px
}
.mainNav .nav li {
float: left
}
.mainNav .nav li a {
float: left;
height: 44px;
line-height: 48px;
color: #fff;
font-size: 16px;
margin: 0 34px;
border-bottom: 2px solid #3eaf7c;
transition: color .3s, background-color .3s, border .3s
}
.mainNav .nav li.on a, .mainNav .nav li a:hover {
border-bottom: 2px solid rgba(255, 255, 255, .8)
}
.footer {
padding: 0 0 20px; /*margin-top: 20px; background: #fbfaf8; border-top: 1px solid #e0e0e0; */
text-align: center;
font-size: 12px
}
.copyright ul li {
color: #999;
line-height: 26px
}
.copyright .menu {
padding: 2px 0 6px;
font-size: 12px
}
.copyright .line {
display: inline-block;
padding: 0 12px;
color: #e5d9da
}
.copyright p {
margin-top: 10px;
color: #999
}
.code_bar img {
margin-left: 66px
}
.rBar {
float: right;
width: 268px
}
.btn_gray, .btn_red, .btn_ora, .btn_ora_white, .btn_red1 {
border-radius: 20px;
font-size: 15px;
display: inline-block;
text-align: center;
cursor: pointer; /*padding: 0 34px; height: 34px; line-height: 34px;*/
padding: 11px 36px;
line-height: 1;
}
.btn_gray {
border: 1px solid #dedede;
background: #fafafa;
}
.btn_red, .btn_ora {
border: 1px solid #3eaf7c;
background: #3eaf7c;
color: #fff
}
.btn_red1 {
border: 1px solid #ff4040;
background: #ff4040;
color: #fff
}
.btn_ora_white {
border: 1px solid #3eaf7c;
color: #3eaf7c
}
.btn_ora_white:hover {
background: #fefaf6
}
.btn_link {
padding: 2px 6px;
background: #3eaf7c;
color: #fff;
border-radius: 2px
}
.btn_gray:hover {
background: #f0f0f0;
color: #333
}
.btn_ora:hover, .btn_red:hover, .btn_link:hover {
background: #3eaf7c;
color: #fff
}
.btn_red1:hover {
background: #fc2525;
color: #fff
}
.pay_Checkout .btn_red, .btn_big {
font-size: 16px;
padding: 15px 0;
border-radius: 4px;
width: 196px;
}
i.vip {
width: 26px;
height: 14px;
text-align: center;
line-height: 14px;
font-size: 11px;
color: #fff;
background: #fe8034;
border-radius: 2px;
margin: 13px 0 0 3px;
display: inline-block;
transform: scale(0.88);
}
i.vip_b {
width: 36px;
height: 22px;
text-align: center;
line-height: 22px;
font-size: 15px;
color: #fff;
background: #3eaf7c;
border-radius: 4px;
margin-left: 5px;
display: inline-block;
vertical-align: 3px
}
.pageBox {
text-align: center;
padding: 20px 0
}
.pageBox a, .pageBox span {
display: inline-block;
color: #999;
padding: 6px 10px;
margin: 0 5px;
border-radius: 4px;
font-size: 14px;
line-height: 1
}
.pageBox .current, .pageBox a:hover {
background: #3eaf7c;
color: #fff
}
.top_nearread {
display: inline-block;
position: relative;
margin-right: 10px;
float: left
}
.top_nearread .nearread {
padding: 0 9px
}
.top_nearread .nearread.on {
border-left: 1px solid #d9d9d9;
border-right: 1px solid #d9d9d9;
background: #FFF;
padding: 0 8px;
height: 36px;
position: relative;
z-index: 8
}
.icon_down {
display: inline-block;
vertical-align: middle;
margin: 2px 0 0 5px;
width: 0px;
height: 0px;
overflow: hidden;
border-width: 4px;
border-style: solid dashed dashed;
border-color: #7f7f7f transparent transparent;
}
.book_record {
width: 382px;
position: absolute;
top: 0;
right: 0;
z-index: 9
}
.record_box {
width: 380px;
background: #fff;
margin-top: 35px;
border: 1px solid #d9d9d9
}
.book_record .sp {
width: 77px;
height: 6px;
background: #fff;
position: absolute;
top: 32px;
right: 1px
}
.record_title {
padding: 14px 10px
}
.record_title a {
border: 1px solid #dedede;
background: #fafafa;
border-radius: 2px;
font-size: 12px;
padding: 6px 12px;
line-height: 1;
margin-right: 14px
}
.record_title a.on {
border: 1px solid #f65167;
background: #f65167;
color: #fff
}
.record_box .all {
display: block;
height: 28px;
line-height: 28px;
text-align: center;
background: #f6f6f6
}
.record_list ul {
margin-bottom: 10px
}
.record_list li {
clear: both;
padding: 10px;
line-height: 1;
overflow: hidden
}
.record_list li:hover {
background: #f6f6f6
}
.record_list li .cover {
width: 50px;
height: 63px;
background: #f6f6f6
}
.record_list li .cover img {
width: 100%;
height: 100%;
}
.record_list a {
display: inline;
color: #333
}
.record_list .book_intro {
width: 300px;
height: 65px;
padding-left: 10px;
position: relative;
}
.record_list .book_intro p {
height: 20px;
line-height: 20px;
overflow: hidden;
color: #999;
}
.record_list .book_intro .p1 {
font-size: 14px;
}
.record_list .book_intro .p2 {
margin: 2px 0;
white-space: nowrap;
text-overflow: ellipsis
}
.record_list .book_intro .p3 {
}
.record_list .book_intro i.vip {
margin: 0 0 0 3px
}
.record_list .read_link a {
color: #fff
}
.manBody {
}
.manBody .mainNav {
background: #3e3d43
}
.manBody .searchBar .s_int {
border: 1px solid #878689;
border-right: none;
background-position: 8px -22px
}
.manBody .mainNav .nav li.on a, .manBody .mainNav .nav li a:hover {
background: #313035
}
.nav_sub {
margin-bottom: 16px
}
.nav_sub a {
padding: 0 6px
}
.copyright .menu a {
color: #666;
font-size: 12px
}
.copyright .menu a:hover, .bookShelf .sj_link:hover {
color: #3eaf7c
}
.rightList .more, .more_bar {
margin: 1px 0;
height: 34px;
line-height: 34px;
border-radius: 1px;
background-color: #f7f7f7;
text-align: center
}
.rightList .more a, .more_bar a {
display: block;
color: #666
}
.header, .footer {
min-width: 1020px
}
/*base*/
.noborder {
border: 0 !important
}
.nomargin {
margin: 0 !important
}
.ml {
margin-left: 12px
}
.mr {
margin-right: 12px
}
.ml5 {
margin-left: 5px
}
.ml10 {
margin-left: 10px
}
.ml15 {
margin-left: 15px
}
.ml20 {
margin-left: 20px
}
.mr5 {
margin-right: 5px
}
.mr10 {
margin-right: 10px
}
.mr15 {
margin-right: 15px
}
.mr20 {
margin-right: 20px
}
.mt5 {
margin-top: 5px
}
.mt10 {
margin-top: 10px
}
.mt15 {
margin-top: 15px
}
.mt20 {
margin-top: 20px
}
.mb5 {
margin-bottom: 5px
}
.mb10 {
margin-bottom: 10px
}
.mb15 {
margin-bottom: 15px
}
.mb20 {
margin-bottom: 20px
}
.mb50 {
margin-bottom: 50px
}
.pointer {
cursor: pointer
}
.notindent {
text-indent: inherit !important
}
.vm {
vertical-align: middle !important
}
.border_t {
border-top: 1px solid #eee
}
.border_b {
border-bottom: 1px solid #eee
}
.border_l {
border-left: 1px solid #eee
}
.border_r {
border-right: 1px solid #eee
}
.layui-laypage-curr {
background: #3eaf7c;
}
.layui-laypage-curr em {
color: #fff;
}
.layui-disabled, .layui-disabled:hover {
color: #d2d2d2 !important;
cursor: not-allowed !important
}
#noFeedbackNote {
line-height: 400px;
text-align: center;
border-top: 1px solid #eee;
}
#txtDescription {
/*width: 900px;*/
height: 288px;
margin: 20px auto 20px;
padding: 10px;
/*新增样式*/
width: 100%;
box-sizing: border-box;
border: 1px solid #eee;
font-size: 14px;
}
.userBox {
margin: 0 auto
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,44 @@
body {
padding-top: 40px;
padding-bottom: 40px;
background-color: #eee;
}
.form-signin {
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
}
.form-signin .checkbox {
font-weight: 400;
}
.form-signin .form-control {
position: relative;
box-sizing: border-box;
height: auto;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}

@ -0,0 +1,643 @@
@charset "utf-8";
.updateTable .style a {
color: #999
}
.updateTable .author a {
color: #999;
cursor: text
}
.bind, .updateTable .style a:hover {
color: #f65167
}
.userBox { /*width: 998px; border: 1px solid #eaeaea;*/
margin: 0 auto 50px;
background: #fff;
border-radius: 6px
}
.channelViewhistory .userBox {
margin: 0 auto
}
.user_l {
width: 350px;
float: left;
padding: 50px 50px
}
.user_l h3 {
font-size: 23px;
font-weight: normal;
line-height: 1;
text-align: center
}
.user_l #LabErr {
color: #ff4040;
display: block;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 14px
}
.user_l .log_list {
width: 350px
}
.user_l .s_input {
margin-bottom: 25px;
font-size: 14px
}
.s_input {
width: 348px;
height: 30px;
line-height: 38px \9;
vertical-align: middle;
border: 1px solid #ddd;
border-radius: 2px
}
.icon_name, .icon_key, .icon_code {
width: 312px;
padding-left: 36px
}
.icon_key {
background-position: 13px -51px
}
.icon_code {
background-position: 13px -117px;
width: 200px;
float: left
}
.code_pic {
height: 38px;
float: right
}
.btn_phone {
height: 40px;
width: 100px;
float: right;
cursor: pointer;
padding: 0;
text-align: center;
border-radius: 2px;
background: #dfdfdf
}
.log_code {
*padding-bottom: 25px
}
.user_l .btn_red {
width: 100%;
font-size: 19px;
padding: 12px
}
.autologin {
color: #999;
line-height: 1;
margin-bottom: 18px
}
.autologin em {
vertical-align: 2px;
margin-left: 4px
}
.user_r {
width: 259px;
margin: 80px 0;
padding: 20px 70px;
border-left: 1px dotted #e3e3e3;
float: right;
text-align: center
}
.user_r .tit {
font-size: 16px;
line-height: 1;
padding: 6px 0 25px
}
.user_r .btn_ora {
padding: 10px 34px
}
.fast_login {
padding: 60px 0 0
}
.fast_list {
text-align: center;
padding: 0.5rem
}
.fast_list li {
display: inline-block;
*display: inline;
zoom: 1
}
.fast_list li .img {
width: 48px;
height: 48px;
margin: 20px 0 5px
}
.fast_list li a:hover {
opacity: 0.8;
filter: alpha(opacity=80);
-moz-opacity: 0.8
}
.fast_list li span {
display: block
}
.fast_list .login_qq {
margin: 0 42px
}
.fast_list .login_wb a {
color: #f55c5b
}
.fast_list .login_qq a {
color: #51b7ff
}
.fast_list .login_wx a {
color: #66d65e
}
.fast_tit {
position: relative;
overflow: hidden
}
.fast_tit .lines {
position: absolute;
top: 50%;
left: 0;
width: 100%;
height: 1px;
line-height: 1;
background: #eaeaea
}
.fast_tit .title {
background: #fff;
font-size: 16px;
padding: 3px 14px;
position: relative;
display: inline-block;
z-index: 999
}
/*userinfo*/
.my_l {
width: 198px;
float: left;
font-size: 13px;
padding-top: 20px;
}
.my_l li a {
display: block;
height: 42px;
line-height: 42px;
padding-left: 62px;
border-left: 4px solid #fff;
margin-bottom: 5px;
color: #666
}
.my_l li .on {
background-color: #fafafa;
border-left: 2px solid #3eaf7c;
color: #000;
border-radius: 0 2px 2px 0
}
.my_l .link_1 {
background-position: 32px -188px
}
.my_l .link_2 {
background-position: 32px -230px
}
.my_l .link_3 {
background-position: 32px -272px
}
.my_l .link_4 {
background-position: 32px -314px
}
.my_l .link_5 {
background-position: 32px -356px
}
.my_l .link_6 {
background-position: 32px -397px
}
.my_l .link_7 {
background-position: 32px -440px
}
.my_l .link_8 {
background-position: 32px -481px
}
.my_r {
width: 739px;
padding: 0 30px 30px;
float: right;
border-left: 1px solid #efefef;
min-height: 470px
}
.my_info {
padding: 30px 0 5px
}
.user_big_head { /*width:110px; height:110px; padding:4px; border:1px solid #eaeaea;*/
margin-right: 30px;
float: left;
width: 80px;
height: 80px;
border-radius: 50%;
}
.my_r .my_name {
font-size: 18px;
line-height: 1;
padding: 5px 0 12px 0
}
.my_r .s_input {
width: 318px;
padding: 0 10px
}
.my_list li {
line-height: 28px
}
.my_list li i, .my_list li em.red {
margin-right: 6px
}
.my_list .binded {
color: #999;
margin-left: 6px
}
.my_list .btn_link {
margin-left: 12px
}
.mytab_list li {
line-height: 30px;
padding: 10px 0;
font-size: 14px
}
.mytab_list li .tit {
width: 70px;
color: #aaa;
text-align: right;
display: inline-block;
margin-right: 18px
}
.mytab_list .user_img {
width: 48px;
height: 48px;
vertical-align: middle;
border-radius: 50%
}
.my_bookshelf .title {
padding: 20px 0 15px;
line-height: 30px
}
.my_bookshelf h4 {
font-size: 14px;
color: #666
}
.my_bookshelf h2 {
font-size: 18px;
font-weight: normal
}
.updateTable {
width: 739px;
color: #999
}
.updateTable table {
width: 100%;
margin-bottom: 14px
}
.updateTable th, .updateTable td {
height: 40px;
line-height: 40px;
vertical-align: middle;
padding-left: 6px;
font-weight: normal;
text-align: left
}
.updateTable th {
background: #f9f9f9;
color: #333;
border-top: 1px solid #eee
}
.updateTable td {
height: 40px;
line-height: 40px
}
.updateTable .style {
width: 80px;
padding-left: 10px
}
.updateTable .name {
width: 178px;
padding-right: 10px
}
.updateTable .name a, .updateTable .chapter a {
max-width: 168px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap
}
.updateTable .chapter {
padding-right: 5px
}
.updateTable .chapter a {
max-width: 220px;
float: left
}
.updateTable .author {
width: 72px;
text-align: left
}
.updateTable .goread {
width: 80px;
text-align: center
}
.updateTable .time {
width: 86px
}
.updateTable .word {
width: 64px;
padding-right: 10px;
text-align: right
}
.updateTable .rank {
width: 30px;
padding-right: 10px;
text-align: center
}
.updateTable .name a, .updateTable .chapter a, .updateTable .author a {
height: 40px;
line-height: 40px;
display: inline-block;
overflow: hidden
}
.updateTable tr:nth-child(2n) td {
background: #fafafa
}
.dataTable {
width: 739px
}
.dataTable table {
width: 100%;
margin-bottom: 14px;
border-collapse: collapse
}
.dataTable th, .dataTable td {
height: 40px;
line-height: 40px;
vertical-align: middle;
padding: 0 10px;
font-weight: normal;
text-align: center;
border: 1px solid #eaeaea
}
.dataTable th {
background: #f8f8f8
}
.nodate {
border-top: 1px solid #eaeaea;
padding: 60px 0
}
.viewhistoryBox { /*padding: 0 30px 30px; */
padding: 0 20px 10px
}
.viewhistoryBox .updateTable {
width: 100%
}
/*.btn_gray, .btn_red, .btn_ora { font-size:14px; padding:8px 28px }*/
.book_tit {
height: 48px;
line-height: 48px;
margin: 0 14px;
border-bottom: 1px solid #eaeaea;
overflow: hidden
}
.book_tit .fl {
font-size: 14px;
color: #999
}
.book_tit .fl h3 {
font-size: 18px;
color: #333;
font-weight: normal;
margin-right: 5px;
display: inline
}
.book_tit .fr {
font-size: 14px
}
.commentBar, .feedback_list {
border-top: 1px solid #eee;
margin-bottom: 15px
}
/*.comment_list { padding: 16px 0; border-bottom: 1px solid #eee }
.comment_list .user_head { width:54px; height:54px; border-radius:50%; float: left; margin-right: 14px }
.comment_list .li_1 { overflow: hidden }
.comment_list .user_name { color: #ed4259 }
.comment_list .li_2 { padding:3px 0; color:#999 }
.comment_list .li_3, .comment_list .li_4 { margin-left:68px }
.comment_list .reply { padding-left: 12px }
.comment_list .num { color: #ed4259; margin: 0 3px }
.comment_list .li_4 { line-height:34px; padding-top:8px; margin-top:15px; border-top:1px solid #eaeaea }
.comment_list .li_4 .more { background:#f7f7f7; border-radius:2px; color:#ed4259; text-align:center }*/
.no_contet {
padding: 190px 0 40px;
text-align: center;
color: #999;
border-top: 1px solid #eee
}
.no_comment {
background: url(../images/no_comment.png) no-repeat center 80px
}
.comment_list {
padding: 20px 0;
border-bottom: 1px solid #eee
}
.comment_list:last-child {
border: none
}
.comment_list .user_heads { /*width: 54px; height: 54px; float: left;*/
position: relative;
margin-right: 20px
}
.comment_list .user_head {
width: 50px;
height: 50px;
border-radius: 50%;
background: #f6f6f6
}
.comment_list .user_heads span {
display: block;
margin: 0;
position: absolute;
left: 12px;
bottom: 0
}
.comment_list ul { /*width: 640px;*/
width: 660px;
}
.comment_list .li_0 {
font-family: "宋体"
}
.comment_list .li_0 strong {
font-size: 14px;
color: #f00
}
.comment_list .li_1 {
overflow: hidden
}
.comment_list .user_name {
color: #ed4259
}
.comment_list .li_2 {
padding: 6px 0
}
.comment_list .li_3 {
color: #999
}
.comment_list .reply {
padding-left: 12px
}
.comment_list .num {
color: #ed4259;
margin: 0 3px
}
.comment_list .li_4 {
line-height: 34px;
padding-top: 8px;
margin-top: 15px;
border-top: 1px solid #eaeaea
}
.pl_bar li {
display: block
}
.pl_bar .name {
color: #666;
padding-top: 2px;
font-size: 14px
}
.pl_bar .dec {
font-size: 14px;
line-height: 1.8;
padding: 12px 0
}
.pl_bar .other {
line-height: 24px;
color: #999;
font-size: 13px
}
.pl_bar .other a {
display: inline-block;
color: #999
}
.pl_bar .reply {
padding-left: 22px;
background: url(../images/icon_reply.png) no-repeat 0 2px
}
/*.no_comment { padding: 70px 14px 115px; color: #CCCCCC; text-align: center; font-size: 14px; }*/
.reply_bar {
background: #f9f9f9;
border: 1px solid #eee;
border-radius: 6px;
padding: 10px;
line-height: 1.8;
}

@ -0,0 +1,153 @@
var $C = function (objName) {
if (typeof (document.getElementById(objName)) != "object")
{ return null; }
else
{ return document.getElementById(objName); }
}
jQuery.cookie = function (name, value, options) {
if (typeof value != 'undefined') {
options = options || {};
if (value === null) {
value = '';
options.expires = -1;
}
var expires = '';
if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
var date;
if (typeof options.expires == 'number') {
date = new Date();
date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
} else {
date = options.expires;
}
expires = '; expires=' + date.toUTCString();
}
var path = options.path ? '; path=' + options.path : '';
var domain = options.domain ? '; domain=' + options.domain : '';
var secure = options.secure ? '; secure' : '';
document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
} else {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
};
$(function () {
$(".rightList li").mouseover(function () {
//$($(this).parent()).children().each(function () {
// $(this).removeClass("on");
//});
//$(this).addClass("on");
});
$(".rightList_nobor li").mouseover(function () {
$($(this).parent()).children().each(function () {
$(this).addClass("on");
});
});
$("#headerUserHistoryBtn").mouseover(function () {
HeaderShowUtil.headerShowHistory();
});
$("#headerUserHistory").mouseleave(function () {
HeaderShowUtil.headerHideHistory();
});
});
function getNote() {
}
function goPage(cpage) {
location.href = '?page=' + cpage;
}
function isWeiXin() {
var ua = window.navigator.userAgent.toLowerCase();
if (ua.indexOf("micromessenger") > 0) {
return true;
} else {
return false;
}
}
var HeaderShowUtil = {
headerShowHistory: function (obj) {
if ($("#headerUserHistory").html().length < 10) {
var rStr = '<div class="record_box">';
rStr += ' <div class="record_title" id="hdShowTitle"><a href="javascript:void(0);" class="record_tit1 on" onclick="javascript:HeaderShowUtil.headerShowHistoryLog(this);"></a><a href="javascript:void(0);" class="record_tit2" onclick="javascript:HeaderShowUtil.headerShowFavLog(this);"></a></div>';
rStr += ' <div class="record_list record_list1" id="hdShowHistory">';
rStr += ' <ul>';
rStr += ' </ul>';
rStr += ' <a class="all" href="/" ></a>';
rStr += ' </div>';
rStr += ' <div class="record_list record_list2" style="display:none" id="hsShowFav">';
rStr += ' <ul>';
rStr += ' </ul>';
rStr += ' <a class="all" href="/" ></a>';
rStr += ' </div>';
rStr += ' <p class="sp"></p>';
rStr += ' </div>';
$("#headerUserHistory").html(rStr);
}
$("#headerUserHistory").show();
$("#headerUserHistoryBtn").addClass("on");
HeaderShowUtil.headerShowHistoryLog();
},
headerHideHistory: function () {
$("#headerUserHistory").hide();
$("#headerUserHistoryBtn").removeClass("on");
},
headerShowHistoryLog: function (obj) {
if (obj != undefined) {
$("#hdShowTitle a").removeClass("on");
$(obj).addClass("on");
$("#hdShowHistory").show();
$("#hsShowFav").hide();
}
var cookieHistory = jQuery.cookie("wapviewhistory");
if (cookieHistory != undefined && cookieHistory.length > 0) {
var bList, bIdList;
var bIdArray = new Array();
var cookieList = cookieHistory.split(',');
for (var i = 0; i < cookieList.length && i < 3; i++) {
var str = cookieList[i];
if (str.indexOf('|') > 0) {
bList = str.split('|');
if (bList.length == 3) {
bIdList += ',' + bList[0].replace("b", "");
bIdArray[bList[0].replace("b", "")] = bList[1];
}
}
}
}
else {
$("#hdShowHistory ul").html("<li>暂无看书历史</li>");
}
},
headerShowFavLog: function (obj) {
$("#hdShowTitle a").removeClass("on");
$(obj).addClass("on");
$("#hsShowFav").show();
$("#hdShowHistory").hide();
var rStr = '';
var uname = jQuery.cookie("waplogname");
if (uname != undefined && uname != "") {
}
else {
rStr = '<li><a href="/user/login.html"></a></li>';
$("#hsShowFav ul").html(rStr);
}
}
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,77 @@
var UserUtil = {
msgStyle: 'background-color:#333; color:#fff; text-align:center; border:none; font-size:20px; padding:10px;',
GetFavoritesNew: function () {
var bIdList = "";
$(".book_list").each(function () {
bIdList += "," + $(this).attr("vals");
});
if (bIdList != "") {
}
},
GetHistory: function () {
var bIdList = "";
$(".book_list").each(function () {
bIdList += "," + $(this).attr("vals");
});
if (bIdList != "") {
}
},
GetChapterInfo: function () {
var cIdList = "";
$(".showCName").each(function () {
cIdList += "," + $(this).attr("vals");
});
if (cIdList != "") {
}
},
SignDay: function () {
if (!signed) {
signed = true;
}
},
SignDayStatus: function () {
},
RegSendSms: function () {
var mob = $("#txtUName").val();
var cCode = $("#TxtChkCode").val();
if (mob != "" && cCode != "") {
$("#btnSendSms").attr("disabled", "disabled");
$("#txtUName").attr("readonly", "true");
}
else {
layer.open({
content: '',
style: UserUtil.msgStyle,
time: 2
});
}
},
GetPassSendSms: function () {
var mob = $("#txtMobile").val();
var cCode = $("#TxtChkCode").val();
if (mob != "" && cCode != "") {
$("#btnSendSms").attr("disabled", "disabled");
$("#txtMobile").attr("readonly", "true");
}
else {
layer.open({
content: '',
style: UserUtil.msgStyle,
time: 2
});
}
},
RegSmsWait: function () {
if (secondStep > 0) {
$("#btnSendSms").val("重新发送(" + secondStep + ")");
secondStep--;
setTimeout("UserUtil.RegSmsWait()", 1000);
}
else {
secondStep = 180;
$("#btnSendSms").val("重新获取验证码");
$("#btnSendSms").removeAttr("disabled");
$("#txtUName").removeAttr("readonly");
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -0,0 +1,2 @@
/** layui-v2.4.5 MIT License By https://www.layui.com */
html #layuicss-skincodecss{display:none;position:absolute;width:1989px}.layui-code-h3,.layui-code-view{position:relative;font-size:12px}.layui-code-view{display:block;margin:10px 0;padding:0;border:1px solid #e2e2e2;border-left-width:6px;background-color:#F2F2F2;color:#333;font-family:Courier New}.layui-code-h3{padding:0 10px;height:32px;line-height:32px;border-bottom:1px solid #e2e2e2}.layui-code-h3 a{position:absolute;right:10px;top:0;color:#999}.layui-code-view .layui-code-ol{position:relative;overflow:auto}.layui-code-view .layui-code-ol li{position:relative;margin-left:45px;line-height:20px;padding:0 5px;border-left:1px solid #e2e2e2;list-style-type:decimal-leading-zero;*list-style-type:decimal;background-color:#fff}.layui-code-view pre{margin:0}.layui-code-notepad{border:1px solid #0C0C0C;border-left-color:#3F3F3F;background-color:#0C0C0C;color:#C2BE9E}.layui-code-notepad .layui-code-h3{border-bottom:none}.layui-code-notepad .layui-code-ol li{background-color:#3F3F3F;border-left:none}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -0,0 +1,919 @@
/** layui-v2.4.5 MIT License By https://www.layui.com */
.layui-layer-imgbar, .layui-layer-imgtit a, .layui-layer-tab .layui-layer-title span, .layui-layer-title {
text-overflow: ellipsis;
white-space: nowrap
}
html #layuicss-layer {
display: none;
position: absolute;
width: 1989px
}
.layui-layer, .layui-layer-shade {
position: fixed;
_position: absolute;
pointer-events: auto
}
.layui-layer-shade {
top: 0;
left: 0;
width: 100%;
height: 100%;
_height: expression(document.body.offsetHeight+"px")
}
.layui-layer {
-webkit-overflow-scrolling: touch;
top: 150px;
left: 0;
margin: 0;
padding: 0;
background-color: #fff;
-webkit-background-clip: content;
border-radius: 2px;
box-shadow: 1px 1px 50px rgba(0, 0, 0, .3)
}
.layui-layer-close {
position: absolute
}
.layui-layer-content {
position: relative
}
.layui-layer-border {
border: 1px solid #B2B2B2;
border: 1px solid rgba(0, 0, 0, .1);
box-shadow: 1px 1px 5px rgba(0, 0, 0, .2)
}
.layui-layer-load {
background: url(loading-1.gif) center center no-repeat #eee
}
.layui-layer-ico {
background: url(icon.png) no-repeat
}
.layui-layer-btn a, .layui-layer-dialog .layui-layer-ico, .layui-layer-setwin a {
display: inline-block;
*display: inline;
*zoom: 1;
vertical-align: top
}
.layui-layer-move {
display: none;
position: fixed;
*position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
cursor: move;
opacity: 0;
filter: alpha(opacity=0);
background-color: #fff;
z-index: 2147483647
}
.layui-layer-resize {
position: absolute;
width: 15px;
height: 15px;
right: 0;
bottom: 0;
cursor: se-resize
}
.layer-anim {
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
-webkit-animation-duration: .3s;
animation-duration: .3s
}
@-webkit-keyframes layer-bounceIn {
0% {
opacity: 0;
-webkit-transform: scale(.5);
transform: scale(.5)
}
100% {
opacity: 1;
-webkit-transform: scale(1);
transform: scale(1)
}
}
@keyframes layer-bounceIn {
0% {
opacity: 0;
-webkit-transform: scale(.5);
-ms-transform: scale(.5);
transform: scale(.5)
}
100% {
opacity: 1;
-webkit-transform: scale(1);
-ms-transform: scale(1);
transform: scale(1)
}
}
.layer-anim-00 {
-webkit-animation-name: layer-bounceIn;
animation-name: layer-bounceIn
}
@-webkit-keyframes layer-zoomInDown {
0% {
opacity: 0;
-webkit-transform: scale(.1) translateY(-2000px);
transform: scale(.1) translateY(-2000px);
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out
}
60% {
opacity: 1;
-webkit-transform: scale(.475) translateY(60px);
transform: scale(.475) translateY(60px);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out
}
}
@keyframes layer-zoomInDown {
0% {
opacity: 0;
-webkit-transform: scale(.1) translateY(-2000px);
-ms-transform: scale(.1) translateY(-2000px);
transform: scale(.1) translateY(-2000px);
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out
}
60% {
opacity: 1;
-webkit-transform: scale(.475) translateY(60px);
-ms-transform: scale(.475) translateY(60px);
transform: scale(.475) translateY(60px);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out
}
}
.layer-anim-01 {
-webkit-animation-name: layer-zoomInDown;
animation-name: layer-zoomInDown
}
@-webkit-keyframes layer-fadeInUpBig {
0% {
opacity: 0;
-webkit-transform: translateY(2000px);
transform: translateY(2000px)
}
100% {
opacity: 1;
-webkit-transform: translateY(0);
transform: translateY(0)
}
}
@keyframes layer-fadeInUpBig {
0% {
opacity: 0;
-webkit-transform: translateY(2000px);
-ms-transform: translateY(2000px);
transform: translateY(2000px)
}
100% {
opacity: 1;
-webkit-transform: translateY(0);
-ms-transform: translateY(0);
transform: translateY(0)
}
}
.layer-anim-02 {
-webkit-animation-name: layer-fadeInUpBig;
animation-name: layer-fadeInUpBig
}
@-webkit-keyframes layer-zoomInLeft {
0% {
opacity: 0;
-webkit-transform: scale(.1) translateX(-2000px);
transform: scale(.1) translateX(-2000px);
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out
}
60% {
opacity: 1;
-webkit-transform: scale(.475) translateX(48px);
transform: scale(.475) translateX(48px);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out
}
}
@keyframes layer-zoomInLeft {
0% {
opacity: 0;
-webkit-transform: scale(.1) translateX(-2000px);
-ms-transform: scale(.1) translateX(-2000px);
transform: scale(.1) translateX(-2000px);
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out
}
60% {
opacity: 1;
-webkit-transform: scale(.475) translateX(48px);
-ms-transform: scale(.475) translateX(48px);
transform: scale(.475) translateX(48px);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out
}
}
.layer-anim-03 {
-webkit-animation-name: layer-zoomInLeft;
animation-name: layer-zoomInLeft
}
@-webkit-keyframes layer-rollIn {
0% {
opacity: 0;
-webkit-transform: translateX(-100%) rotate(-120deg);
transform: translateX(-100%) rotate(-120deg)
}
100% {
opacity: 1;
-webkit-transform: translateX(0) rotate(0);
transform: translateX(0) rotate(0)
}
}
@keyframes layer-rollIn {
0% {
opacity: 0;
-webkit-transform: translateX(-100%) rotate(-120deg);
-ms-transform: translateX(-100%) rotate(-120deg);
transform: translateX(-100%) rotate(-120deg)
}
100% {
opacity: 1;
-webkit-transform: translateX(0) rotate(0);
-ms-transform: translateX(0) rotate(0);
transform: translateX(0) rotate(0)
}
}
.layer-anim-04 {
-webkit-animation-name: layer-rollIn;
animation-name: layer-rollIn
}
@keyframes layer-fadeIn {
0% {
opacity: 0
}
100% {
opacity: 1
}
}
.layer-anim-05 {
-webkit-animation-name: layer-fadeIn;
animation-name: layer-fadeIn
}
@-webkit-keyframes layer-shake {
0%, 100% {
-webkit-transform: translateX(0);
transform: translateX(0)
}
10%, 30%, 50%, 70%, 90% {
-webkit-transform: translateX(-10px);
transform: translateX(-10px)
}
20%, 40%, 60%, 80% {
-webkit-transform: translateX(10px);
transform: translateX(10px)
}
}
@keyframes layer-shake {
0%, 100% {
-webkit-transform: translateX(0);
-ms-transform: translateX(0);
transform: translateX(0)
}
10%, 30%, 50%, 70%, 90% {
-webkit-transform: translateX(-10px);
-ms-transform: translateX(-10px);
transform: translateX(-10px)
}
20%, 40%, 60%, 80% {
-webkit-transform: translateX(10px);
-ms-transform: translateX(10px);
transform: translateX(10px)
}
}
.layer-anim-06 {
-webkit-animation-name: layer-shake;
animation-name: layer-shake
}
@-webkit-keyframes fadeIn {
0% {
opacity: 0
}
100% {
opacity: 1
}
}
.layui-layer-title {
padding: 0 80px 0 20px;
height: 42px;
line-height: 42px;
border-bottom: 1px solid #eee;
font-size: 14px;
color: #333;
overflow: hidden;
background-color: #F8F8F8;
border-radius: 2px 2px 0 0
}
.layui-layer-setwin {
position: absolute;
right: 15px;
*right: 0;
top: 15px;
font-size: 0;
line-height: initial
}
.layui-layer-setwin a {
position: relative;
width: 16px;
height: 16px;
margin-left: 10px;
font-size: 12px;
_overflow: hidden
}
.layui-layer-setwin .layui-layer-min cite {
position: absolute;
width: 14px;
height: 2px;
left: 0;
top: 50%;
margin-top: -1px;
background-color: #2E2D3C;
cursor: pointer;
_overflow: hidden
}
.layui-layer-setwin .layui-layer-min:hover cite {
background-color: #2D93CA
}
.layui-layer-setwin .layui-layer-max {
background-position: -32px -40px
}
.layui-layer-setwin .layui-layer-max:hover {
background-position: -16px -40px
}
.layui-layer-setwin .layui-layer-maxmin {
background-position: -65px -40px
}
.layui-layer-setwin .layui-layer-maxmin:hover {
background-position: -49px -40px
}
.layui-layer-setwin .layui-layer-close1 {
background-position: 1px -40px;
cursor: pointer
}
.layui-layer-setwin .layui-layer-close1:hover {
opacity: .7
}
.layui-layer-setwin .layui-layer-close2 {
position: absolute;
right: -28px;
top: -28px;
width: 30px;
height: 30px;
margin-left: 0;
background-position: -149px -31px;
*right: -18px;
_display: none
}
.layui-layer-setwin .layui-layer-close2:hover {
background-position: -180px -31px
}
.layui-layer-btn {
text-align: right;
padding: 0 15px 12px;
pointer-events: auto;
user-select: none;
-webkit-user-select: none
}
.layui-layer-btn a {
height: 28px;
line-height: 28px;
margin: 5px 5px 0;
padding: 0 15px;
border: 1px solid #dedede;
background-color: #fff;
color: #333;
border-radius: 2px;
font-weight: 400;
cursor: pointer;
text-decoration: none
}
.layui-layer-btn a:hover {
opacity: .9;
text-decoration: none
}
.layui-layer-btn a:active {
opacity: .8
}
.layui-layer-btn .layui-layer-btn0 {
border-color: #3eaf7c;
background-color: #3eaf7c;
color: #fff
}
.layui-layer-btn-l {
text-align: left
}
.layui-layer-btn-c {
text-align: center
}
.layui-layer-dialog {
min-width: 260px
}
.layui-layer-dialog .layui-layer-content {
position: relative;
padding: 20px;
line-height: 24px;
word-break: break-all;
overflow: hidden;
font-size: 14px;
overflow-x: hidden;
overflow-y: auto
}
.layui-layer-dialog .layui-layer-content .layui-layer-ico {
position: absolute;
top: 16px;
left: 15px;
_left: -40px;
width: 30px;
height: 30px
}
.layui-layer-ico1 {
background-position: -30px 0
}
.layui-layer-ico2 {
background-position: -60px 0
}
.layui-layer-ico3 {
background-position: -90px 0
}
.layui-layer-ico4 {
background-position: -120px 0
}
.layui-layer-ico5 {
background-position: -150px 0
}
.layui-layer-ico6 {
background-position: -180px 0
}
.layui-layer-rim {
border: 6px solid #8D8D8D;
border: 6px solid rgba(0, 0, 0, .3);
border-radius: 5px;
box-shadow: none
}
.layui-layer-msg {
min-width: 180px;
border: 1px solid #D3D4D3;
box-shadow: none
}
.layui-layer-hui {
min-width: 100px;
background-color: #000;
filter: alpha(opacity=60);
background-color: rgba(0, 0, 0, .6);
color: #fff;
border: none
}
.layui-layer-hui .layui-layer-content {
padding: 12px 25px;
text-align: center
}
.layui-layer-dialog .layui-layer-padding {
padding: 20px 20px 20px 55px;
text-align: left
}
.layui-layer-page .layui-layer-content {
position: relative;
overflow: auto
}
.layui-layer-iframe .layui-layer-btn, .layui-layer-page .layui-layer-btn {
padding-top: 10px
}
.layui-layer-nobg {
background: 0 0
}
.layui-layer-iframe iframe {
display: block;
width: 100%
}
.layui-layer-loading {
border-radius: 100%;
background: 0 0;
box-shadow: none;
border: none
}
.layui-layer-loading .layui-layer-content {
width: 60px;
height: 24px;
background: url(loading-0.gif) no-repeat
}
.layui-layer-loading .layui-layer-loading1 {
width: 37px;
height: 37px;
background: url(loading-1.gif) no-repeat
}
.layui-layer-ico16, .layui-layer-loading .layui-layer-loading2 {
width: 32px;
height: 32px;
background: url(loading-2.gif) no-repeat
}
.layui-layer-tips {
background: 0 0;
box-shadow: none;
border: none
}
.layui-layer-tips .layui-layer-content {
position: relative;
line-height: 22px;
min-width: 12px;
padding: 8px 15px;
font-size: 12px;
_float: left;
border-radius: 2px;
box-shadow: 1px 1px 3px rgba(0, 0, 0, .2);
background-color: #000;
color: #fff
}
.layui-layer-tips .layui-layer-close {
right: -2px;
top: -1px
}
.layui-layer-tips i.layui-layer-TipsG {
position: absolute;
width: 0;
height: 0;
border-width: 8px;
border-color: transparent;
border-style: dashed;
*overflow: hidden
}
.layui-layer-tips i.layui-layer-TipsB, .layui-layer-tips i.layui-layer-TipsT {
left: 5px;
border-right-style: solid;
border-right-color: #000
}
.layui-layer-tips i.layui-layer-TipsT {
bottom: -8px
}
.layui-layer-tips i.layui-layer-TipsB {
top: -8px
}
.layui-layer-tips i.layui-layer-TipsL, .layui-layer-tips i.layui-layer-TipsR {
top: 5px;
border-bottom-style: solid;
border-bottom-color: #000
}
.layui-layer-tips i.layui-layer-TipsR {
left: -8px
}
.layui-layer-tips i.layui-layer-TipsL {
right: -8px
}
.layui-layer-lan[type=dialog] {
min-width: 280px
}
.layui-layer-lan .layui-layer-title {
background: #4476A7;
color: #fff;
border: none
}
.layui-layer-lan .layui-layer-btn {
padding: 5px 10px 10px;
text-align: right;
border-top: 1px solid #E9E7E7
}
.layui-layer-lan .layui-layer-btn a {
background: #fff;
border-color: #E9E7E7;
color: #333
}
.layui-layer-lan .layui-layer-btn .layui-layer-btn1 {
background: #C9C5C5
}
.layui-layer-molv .layui-layer-title {
background: #009f95;
color: #fff;
border: none
}
.layui-layer-molv .layui-layer-btn a {
background: #009f95;
border-color: #009f95
}
.layui-layer-molv .layui-layer-btn .layui-layer-btn1 {
background: #92B8B1
}
.layui-layer-iconext {
background: url(icon-ext.png) no-repeat
}
.layui-layer-prompt .layui-layer-input {
display: block;
width: 230px;
height: 36px;
margin: 0 auto;
line-height: 30px;
padding-left: 10px;
border: 1px solid #e6e6e6;
color: #333
}
.layui-layer-prompt textarea.layui-layer-input {
width: 300px;
height: 100px;
line-height: 20px;
padding: 6px 10px
}
.layui-layer-prompt .layui-layer-content {
padding: 20px
}
.layui-layer-prompt .layui-layer-btn {
padding-top: 0
}
.layui-layer-tab {
box-shadow: 1px 1px 50px rgba(0, 0, 0, .4)
}
.layui-layer-tab .layui-layer-title {
padding-left: 0;
overflow: visible
}
.layui-layer-tab .layui-layer-title span {
position: relative;
float: left;
min-width: 80px;
max-width: 260px;
padding: 0 20px;
text-align: center;
overflow: hidden;
cursor: pointer
}
.layui-layer-tab .layui-layer-title span.layui-this {
height: 43px;
border-left: 1px solid #eee;
border-right: 1px solid #eee;
background-color: #fff;
z-index: 10
}
.layui-layer-tab .layui-layer-title span:first-child {
border-left: none
}
.layui-layer-tabmain {
line-height: 24px;
clear: both
}
.layui-layer-tabmain .layui-layer-tabli {
display: none
}
.layui-layer-tabmain .layui-layer-tabli.layui-this {
display: block
}
.layui-layer-photos {
-webkit-animation-duration: .8s;
animation-duration: .8s
}
.layui-layer-photos .layui-layer-content {
overflow: hidden;
text-align: center
}
.layui-layer-photos .layui-layer-phimg img {
position: relative;
width: 100%;
display: inline-block;
*display: inline;
*zoom: 1;
vertical-align: top
}
.layui-layer-imgbar, .layui-layer-imguide {
display: none
}
.layui-layer-imgnext, .layui-layer-imgprev {
position: absolute;
top: 50%;
width: 27px;
_width: 44px;
height: 44px;
margin-top: -22px;
outline: 0;
blr: expression(this.onFocus=this.blur())
}
.layui-layer-imgprev {
left: 10px;
background-position: -5px -5px;
_background-position: -70px -5px
}
.layui-layer-imgprev:hover {
background-position: -33px -5px;
_background-position: -120px -5px
}
.layui-layer-imgnext {
right: 10px;
_right: 8px;
background-position: -5px -50px;
_background-position: -70px -50px
}
.layui-layer-imgnext:hover {
background-position: -33px -50px;
_background-position: -120px -50px
}
.layui-layer-imgbar {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
height: 32px;
line-height: 32px;
background-color: rgba(0, 0, 0, .8);
background-color: #000 \9;
filter: Alpha(opacity=80);
color: #fff;
overflow: hidden;
font-size: 0
}
.layui-layer-imgtit * {
display: inline-block;
*display: inline;
*zoom: 1;
vertical-align: top;
font-size: 12px
}
.layui-layer-imgtit a {
max-width: 65%;
overflow: hidden;
color: #fff
}
.layui-layer-imgtit a:hover {
color: #fff;
text-decoration: underline
}
.layui-layer-imgtit em {
padding-left: 10px;
font-style: normal
}
@-webkit-keyframes layer-bounceOut {
100% {
opacity: 0;
-webkit-transform: scale(.7);
transform: scale(.7)
}
30% {
-webkit-transform: scale(1.05);
transform: scale(1.05)
}
0% {
-webkit-transform: scale(1);
transform: scale(1)
}
}
@keyframes layer-bounceOut {
100% {
opacity: 0;
-webkit-transform: scale(.7);
-ms-transform: scale(.7);
transform: scale(.7)
}
30% {
-webkit-transform: scale(1.05);
-ms-transform: scale(1.05);
transform: scale(1.05)
}
0% {
-webkit-transform: scale(1);
-ms-transform: scale(1);
transform: scale(1)
}
}
.layer-anim-close {
-webkit-animation-name: layer-bounceOut;
animation-name: layer-bounceOut;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
-webkit-animation-duration: .2s;
animation-duration: .2s
}
@media screen and (max-width: 1100px) {
.layui-layer-iframe {
overflow-y: auto;
-webkit-overflow-scrolling: touch
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

@ -0,0 +1,96 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title></title>
<link rel="stylesheet" href="http://local.res.layui.com/layui/src/css/layui.css">
<style>
body .layim-chat-main{height: auto;}
</style>
</head>
<body>
<div class="layim-chat-main">
<ul id="LAY_view"></ul>
</div>
<div id="LAY_page" style="margin: 0 10px;"></div>
<textarea title="消息模版" id="LAY_tpl" style="display:none;">
{{# layui.each(d.data, function(index, item){
if(item.id == parent.layui.layim.cache().mine.id){ }}
<li class="layim-chat-mine"><div class="layim-chat-user"><img src="{{ item.avatar }}"><cite><i>{{ layui.data.date(item.timestamp) }}</i>{{ item.username }}</cite></div><div class="layim-chat-text">{{ layui.layim.content(item.content) }}</div></li>
{{# } else { }}
<li><div class="layim-chat-user"><img src="{{ item.avatar }}"><cite>{{ item.username }}<i>{{ layui.data.date(item.timestamp) }}</i></cite></div><div class="layim-chat-text">{{ layui.layim.content(item.content) }}</div></li>
{{# }
}); }}
</textarea>
<!--
laytpl http://www.layui.com/doc/modules/laytpl.html
-->
<script src="http://local.res.layui.com/layui/src/layui.js"></script>
<script>
layui.use(['layim', 'laypage'], function(){
var layim = layui.layim
,layer = layui.layer
,laytpl = layui.laytpl
,$ = layui.jquery
,laypage = layui.laypage;
//聊天记录的分页此处不做演示你可以采用laypage不了解的同学见文档http://www.layui.com/doc/modules/laypage.html
//开始请求聊天记录
var param = location.search //获得URL参数。该窗口url会携带会话id和type他们是你请求聊天记录的重要凭据
//实际使用时下述的res一般是通过Ajax获得而此处仅仅只是演示数据格式
,res = {
code: 0
,msg: ''
,data: [{
username: ''
,id: 100000
,avatar: 'http://tva3.sinaimg.cn/crop.0.0.512.512.180/8693225ajw8f2rt20ptykj20e80e8weu.jpg'
,timestamp: 1480897882000
,content: 'face[] face[] '
}, {
username: 'Z_'
,id: 108101
,avatar: 'http://tva3.sinaimg.cn/crop.0.0.512.512.180/8693225ajw8f2rt20ptykj20e80e8weu.jpg'
,timestamp: 1480897892000
,content: 'face[]'
},{
username: 'Z_'
,id: 108101
,avatar: 'http://tva3.sinaimg.cn/crop.0.0.512.512.180/8693225ajw8f2rt20ptykj20e80e8weu.jpg'
,timestamp: 1480897898000
,content: '~'
},{
username: 'Z_'
,id: 108101
,avatar: 'http://tva3.sinaimg.cn/crop.0.0.512.512.180/8693225ajw8f2rt20ptykj20e80e8weu.jpg'
,timestamp: 1480897908000
,content: '使\nlayui.js\n/css/modules/layim/html/chatlog.html'
}]
}
//console.log(param)
var html = laytpl(LAY_tpl.value).render({
data: res.data
});
$('#LAY_view').html(html);
});
</script>
</body>
</html>

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title></title>
<link rel="stylesheet" href="http://local.res.layui.com/layui/src/css/layui.css">
<style>
</style>
</head>
<body>
<div style="margin: 15px;">
<blockquote class="layui-elem-quote">使
<br>layui.js/css/modules/layim/html/find.html</blockquote>
</div>
<script src="http://local.res.layui.com/layui/src/layui.js"></script>
<script>
layui.use(['layim', 'laypage'], function(){
var layim = layui.layim
,layer = layui.layer
,laytpl = layui.laytpl
,$ = layui.jquery
,laypage = layui.laypage;
//一些添加好友请求之类的交互参见文档
});
</script>
</body>
</html>

@ -0,0 +1,87 @@
{
"code": 0,
"pages": 1,
"data": [
{
"id": 76,
"content": "申请添加你为好友",
"uid": 168,
"from": 166488,
"from_group": 0,
"type": 1,
"remark": "有问题要问",
"href": null,
"read": 1,
"time": "刚刚",
"user": {
"id": 166488,
"avatar": "http://q.qlogo.cn/qqapp/101235792/B704597964F9BD0DB648292D1B09F7E8/100",
"username": "李彦宏",
"sign": null
}
},
{
"id": 75,
"content": "申请添加你为好友",
"uid": 168,
"from": 347592,
"from_group": 0,
"type": 1,
"remark": "你好啊!",
"href": null,
"read": 1,
"time": "刚刚",
"user": {
"id": 347592,
"avatar": "http://q.qlogo.cn/qqapp/101235792/B78751375E0531675B1272AD994BA875/100",
"username": "麻花疼",
"sign": null
}
},
{
"id": 62,
"content": "雷军 拒绝了你的好友申请",
"uid": 168,
"from": null,
"from_group": null,
"type": 1,
"remark": null,
"href": null,
"read": 1,
"time": "10天前",
"user": {
"id": null
}
},
{
"id": 60,
"content": "马小云 已经同意你的好友申请",
"uid": 168,
"from": null,
"from_group": null,
"type": 1,
"remark": null,
"href": null,
"read": 1,
"time": "10天前",
"user": {
"id": null
}
},
{
"id": 61,
"content": "贤心 已经同意你的好友申请",
"uid": 168,
"from": null,
"from_group": null,
"type": 1,
"remark": null,
"href": null,
"read": 1,
"time": "10天前",
"user": {
"id": null
}
}
]
}

@ -0,0 +1,208 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title></title>
<link rel="stylesheet" href="../../../layui.css?v=1">
<style>
.layim-msgbox{margin: 15px;}
.layim-msgbox li{position: relative; margin-bottom: 10px; padding: 0 130px 10px 60px; padding-bottom: 10px; line-height: 22px; border-bottom: 1px dotted #e2e2e2;}
.layim-msgbox .layim-msgbox-tips{margin: 0; padding: 10px 0; border: none; text-align: center; color: #999;}
.layim-msgbox .layim-msgbox-system{padding: 0 10px 10px 10px;}
.layim-msgbox li p span{padding-left: 5px; color: #999;}
.layim-msgbox li p em{font-style: normal; color: #FF5722;}
.layim-msgbox-avatar{position: absolute; left: 0; top: 0; width: 50px; height: 50px;}
.layim-msgbox-user{padding-top: 5px;}
.layim-msgbox-content{margin-top: 3px;}
.layim-msgbox .layui-btn-small{padding: 0 15px; margin-left: 5px;}
.layim-msgbox-btn{position: absolute; right: 0; top: 12px; color: #999;}
</style>
</head>
<body>
<ul class="layim-msgbox" id="LAY_view"></ul>
<div style="margin: 0 15px;">
<blockquote class="layui-elem-quote">使
<br>layui.js/css/modules/layim/html/msgbox.html</blockquote>
</div>
<textarea title="消息模版" id="LAY_tpl" style="display:none;">
{{# layui.each(d.data, function(index, item){
if(item.from){ }}
<li data-uid="{{ item.from }}" data-fromGroup="{{ item.from_group }}">
<a href="/u/{{ item.from }}/" >
<img src="{{ item.user.avatar }}" class="layui-circle layim-msgbox-avatar">
</a>
<p class="layim-msgbox-user">
<a href="/u/{{ item.from }}/" >{{ item.user.username||'' }}</a>
<span>{{ item.time }}</span>
</p>
<p class="layim-msgbox-content">
{{ item.content }}
<span>{{ item.remark ? ': '+item.remark : '' }}</span>
</p>
<p class="layim-msgbox-btn">
<button class="layui-btn layui-btn-small" data-type="agree"></button>
<button class="layui-btn layui-btn-small layui-btn-primary" data-type="refuse"></button>
</p>
</li>
{{# } else { }}
<li class="layim-msgbox-system">
<p><em></em>{{ item.content }}<span>{{ item.time }}</span></p>
</li>
{{# }
}); }}
</textarea>
<!--
laytpl http://www.layui.com/doc/modules/laytpl.html
-->
<script src="../../../../layui.js?v=1"></script>
<script>
layui.use(['layim', 'flow'], function(){
var layim = layui.layim
,layer = layui.layer
,laytpl = layui.laytpl
,$ = layui.jquery
,flow = layui.flow;
var cache = {}; //用于临时记录请求到的数据
//请求消息
var renderMsg = function(page, callback){
//实际部署时,请将下述 getmsg.json 改为你的接口地址
$.get('getmsg.json', {
page: page || 1
}, function(res){
if(res.code != 0){
return layer.msg(res.msg);
}
//记录来源用户信息
layui.each(res.data, function(index, item){
cache[item.from] = item.user;
});
callback && callback(res.data, res.pages);
});
};
//消息信息流
flow.load({
elem: '#LAY_view' //流加载容器
,isAuto: false
,end: '<li class="layim-msgbox-tips"></li>'
,done: function(page, next){ //加载下一页
renderMsg(page, function(data, pages){
var html = laytpl(LAY_tpl.value).render({
data: data
,page: page
});
next(html, page < pages);
});
}
});
//打开页面即把消息标记为已读
/*
$.post('/message/read', {
type: 1
});
*/
//操作
var active = {
//同意
agree: function(othis){
var li = othis.parents('li')
,uid = li.data('uid')
,from_group = li.data('fromGroup')
,user = cache[uid];
//选择分组
parent.layui.layim.setFriendGroup({
type: 'friend'
,username: user.username
,avatar: user.avatar
,group: parent.layui.layim.cache().friend //获取好友分组数据
,submit: function(group, index){
//将好友追加到主面板
parent.layui.layim.addList({
type: 'friend'
,avatar: user.avatar //好友头像
,username: user.username //好友昵称
,groupid: group //所在的分组id
,id: uid //好友ID
,sign: user.sign //好友签名
});
parent.layer.close(index);
othis.parent().html('');
//实际部署时,请开启下述注释,并改成你的接口地址
/*
$.post('/im/agreeFriend', {
uid: uid //对方用户ID
,from_group: from_group //对方设定的好友分组
,group: group //我设定的好友分组
}, function(res){
if(res.code != 0){
return layer.msg(res.msg);
}
//将好友追加到主面板
parent.layui.layim.addList({
type: 'friend'
,avatar: user.avatar //好友头像
,username: user.username //好友昵称
,groupid: group //所在的分组id
,id: uid //好友ID
,sign: user.sign //好友签名
});
parent.layer.close(index);
othis.parent().html('');
});
*/
}
});
}
//拒绝
,refuse: function(othis){
var li = othis.parents('li')
,uid = li.data('uid');
layer.confirm('', function(index){
$.post('/im/refuseFriend', {
uid: uid //对方用户ID
}, function(res){
if(res.code != 0){
return layer.msg(res.msg);
}
layer.close(index);
othis.parent().html('<em></em>');
});
});
}
};
$('body').on('click', '.layui-btn', function(){
var othis = $(this), type = othis.data('type');
active[type] ? active[type].call(this, othis) : '';
});
});
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save