├── .eslintignore
├── screenshot
├── home.gif
├── home2.gif
├── sign.gif
├── write.gif
├── detail.gif
├── follow.gif
└── introduce.gif
├── app.js
├── .gitattributes
├── .idea
└── vcs.xml
├── config
├── config.unittest.js
├── plugin.js
└── config.default.js
├── .travis.yml
├── .gitignore
├── app
├── extend
│ ├── helper.js
│ └── context.js
├── service
│ ├── user.js
│ ├── login.js
│ ├── articleType.js
│ ├── comment.js
│ └── article.js
├── controller
│ ├── admin
│ │ └── login.js
│ ├── page.js
│ ├── client
│ │ └── login.js
│ ├── users.js
│ ├── articleType.js
│ └── article.js
├── middleware
│ └── auth.js
├── model
│ ├── articleTypes.js
│ ├── commentLove.js
│ ├── articleBack.js
│ ├── users.js
│ ├── commentReply.js
│ ├── comment.js
│ └── article.js
└── router.js
├── .eslintrc
├── .sequelizerc
├── appveyor.yml
├── database
├── config.json
├── migrations
│ ├── 20181217130011-init-comment-loves.js
│ ├── 20181203033901-init-article-types.js
│ ├── 20181216033631-init-comments.js
│ ├── 20181216033658-init-comment-replys.js
│ ├── 20181213062911-init-articles-back.js
│ ├── 20181129051931-init-users.js
│ └── 20181130061343-init-articles.js
└── create.md
├── .autod.conf.js
├── test
└── app
│ └── controller
│ └── home.test.js
├── package.json
├── README.md
└── db
└── db.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | coverage
2 |
--------------------------------------------------------------------------------
/screenshot/home.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jieyuanfei/jianshu-egg-api/HEAD/screenshot/home.gif
--------------------------------------------------------------------------------
/screenshot/home2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jieyuanfei/jianshu-egg-api/HEAD/screenshot/home2.gif
--------------------------------------------------------------------------------
/screenshot/sign.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jieyuanfei/jianshu-egg-api/HEAD/screenshot/sign.gif
--------------------------------------------------------------------------------
/screenshot/write.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jieyuanfei/jianshu-egg-api/HEAD/screenshot/write.gif
--------------------------------------------------------------------------------
/screenshot/detail.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jieyuanfei/jianshu-egg-api/HEAD/screenshot/detail.gif
--------------------------------------------------------------------------------
/screenshot/follow.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jieyuanfei/jianshu-egg-api/HEAD/screenshot/follow.gif
--------------------------------------------------------------------------------
/screenshot/introduce.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jieyuanfei/jianshu-egg-api/HEAD/screenshot/introduce.gif
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | module.exports = app => {
2 | app.beforeStart(async () => {
3 | // 应用会等待这个函数执行完成才启动
4 | console.log('=====初始化======')
5 | })
6 | }
7 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.js linguist-language=javascript
2 | *.css linguist-language=javascript
3 | *.scss linguist-language=javascript
4 | *.html linguist-language=javascript
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/config/config.unittest.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | exports.sequelize = {
4 | dialect: 'mysql', // support: mysql, mariadb, postgres, mssql
5 | database: 'egg-sequelize-example-unittest',
6 | };
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - '8'
5 | install:
6 | - npm i npminstall && npminstall
7 | script:
8 | - npm run ci
9 | after_script:
10 | - npminstall codecov && codecov
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | logs/
2 | npm-debug.log
3 | yarn-error.log
4 | node_modules/
5 | package-lock.json
6 | yarn.lock
7 | coverage/
8 | .idea/
9 | config/
10 | run/
11 | .DS_Store
12 | *.sw*
13 | *.un~
14 | config/config.default.js
15 |
--------------------------------------------------------------------------------
/app/extend/helper.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | parseInt(string) {
5 | if (typeof string === 'number') return string;
6 | if (!string) return string;
7 | return parseInt(string) || 0;
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-egg",
3 | "rules": {
4 | "strict": 0,
5 | "linebreak-style": 0,
6 | "semi": 0,
7 | "comma-dangle": 0,
8 | "object-shorthand": 0,
9 | "prefer-const": 0
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.sequelizerc:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 |
5 | module.exports = {
6 | config: path.join(__dirname, 'database/config.json'),
7 | 'migrations-path': path.join(__dirname, 'database/migrations'),
8 | 'seeders-path': path.join(__dirname, 'database/seeders'),
9 | };
10 |
--------------------------------------------------------------------------------
/config/plugin.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // had enabled by egg
4 | // exports.static = true;
5 | // module.exports = {
6 | // mongoose: {
7 | // enable: true,
8 | // package: 'egg-mongoose'
9 | // }
10 | // }
11 | exports.sequelize = {
12 | enable: true,
13 | package: 'egg-sequelize',
14 | };
15 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | matrix:
3 | - nodejs_version: '8'
4 |
5 | install:
6 | - ps: Install-Product node $env:nodejs_version
7 | - npm i npminstall && node_modules\.bin\npminstall
8 |
9 | test_script:
10 | - node --version
11 | - npm --version
12 | - npm run test
13 |
14 | build: off
15 |
--------------------------------------------------------------------------------
/database/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "development": {
3 | "username": "root",
4 | "password": "123",
5 | "database": "egg-sequelize-example-dev",
6 | "host": "127.0.0.1",
7 | "dialect": "mysql"
8 | },
9 | "test": {
10 | "username": "root",
11 | "password": "123",
12 | "database": "egg-sequelize-example-unittest",
13 | "host": "127.0.0.1",
14 | "dialect": "mysql"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/service/user.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Service = require('egg').Service;
4 |
5 | class User extends Service {
6 |
7 |
8 | async find(id) {
9 | const user = await this.ctx.model.Users.findById(id);
10 | if (!user) {
11 | return {
12 | code: 404,
13 | msg: '用户不存在'
14 | }
15 | }
16 | return {
17 | code: 0,
18 | data: user
19 | };
20 | }
21 | }
22 |
23 | module.exports = User;
24 |
--------------------------------------------------------------------------------
/app/controller/admin/login.js:
--------------------------------------------------------------------------------
1 | const Controller = require('egg').Controller
2 | class LoginController extends Controller {
3 | // 获取验证码
4 | async getCaptcha() {
5 | const { ctx } = this
6 | const captcha = ctx.service.login.genCaptcha()
7 | // 把生成的验证码文本信息(如:t8ec),存入session,以待验证
8 | ctx.session.code = captcha.text
9 | // 将生成的验证码svg图片返回给前端
10 | ctx.body = captcha.data
11 | }
12 | }
13 |
14 | module.exports = LoginController
15 |
--------------------------------------------------------------------------------
/app/middleware/auth.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken')
2 | module.exports = () => {
3 | return async function auth(ctx, next) {
4 | try {
5 | let decode = jwt.verify(ctx.get('Authorization'), ctx.app.config.jwt.cert)
6 | ctx.userId = decode.id
7 | } catch (err) {
8 | console.log('登录权限获取失败' + err)
9 | ctx.error(401, '授权失败,请重新登录')
10 | return
11 | }
12 | await next() // 这里因为next之后的操作是异步的所以需要加 await
13 | };
14 | };
15 |
--------------------------------------------------------------------------------
/.autod.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | write: true,
5 | prefix: '^',
6 | plugin: 'autod-egg',
7 | test: [
8 | 'test',
9 | 'benchmark',
10 | ],
11 | dep: [
12 | 'egg',
13 | 'egg-scripts',
14 | ],
15 | devdep: [
16 | 'egg-ci',
17 | 'egg-bin',
18 | 'egg-mock',
19 | 'autod',
20 | 'autod-egg',
21 | 'eslint',
22 | 'eslint-config-egg',
23 | 'webstorm-disable-index',
24 | ],
25 | exclude: [
26 | './test/fixtures',
27 | './dist',
28 | ],
29 | };
30 |
31 |
--------------------------------------------------------------------------------
/test/app/controller/home.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const { app, assert } = require('egg-mock/bootstrap');
4 |
5 | describe('test/app/controller/home.test.js', () => {
6 |
7 | it('should assert', function* () {
8 | const pkg = require('../../../package.json');
9 | assert(app.config.keys.startsWith(pkg.name));
10 |
11 | // const ctx = app.mockContext({});
12 | // yield ctx.service.xx();
13 | });
14 |
15 | it('should GET /', () => {
16 | return app.httpRequest()
17 | .get('/')
18 | .expect('hi, egasdfg')
19 | .expect(200);
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/database/migrations/20181217130011-init-comment-loves.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | // 在执行数据库升级时调用的函数,创建 t_comment_loves 表
5 | up: async (queryInterface, Sequelize) => {
6 | const { INTEGER, DATE } = Sequelize;
7 | await queryInterface.createTable('t_comment_loves', {
8 | id: { type: INTEGER, primaryKey: true, autoIncrement: true },
9 | comment_id: INTEGER,
10 | user_id: INTEGER,
11 | created_at: DATE,
12 | updated_at: DATE
13 | });
14 | },
15 | // 在执行数据库降级时调用的函数,删除 t_comment_loves 表
16 | down: async queryInterface => {
17 | await queryInterface.dropTable('t_comment_loves');
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/database/migrations/20181203033901-init-article-types.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | // 在执行数据库升级时调用的函数,创建 users 表
5 | up: async (queryInterface, Sequelize) => {
6 | const { INTEGER, DATE, STRING } = Sequelize;
7 | await queryInterface.createTable('t_article_types', {
8 | id: { type: INTEGER, primaryKey: true, autoIncrement: true },
9 | type_name: STRING(255),
10 | user_id: INTEGER,
11 | delete: INTEGER(2),
12 | created_at: DATE,
13 | updated_at: DATE,
14 | });
15 | },
16 | // 在执行数据库降级时调用的函数,删除 users 表
17 | down: async queryInterface => {
18 | await queryInterface.dropTable('t_article_types');
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/app/extend/context.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | result(data = {}, success = true, code = 0, msg = '') {
5 | let resultJson = {
6 | success: success,
7 | data: data,
8 | code: code,
9 | msg: msg,
10 | }
11 | this.body = resultJson;
12 | },
13 | success(data = {}) {
14 | let resultJson = {
15 | success: true,
16 | data: data,
17 | code: 0,
18 | msg: '',
19 | }
20 | this.body = resultJson;
21 | },
22 | error(code = 0, msg = '') {
23 | let resultJson = {
24 | success: false,
25 | data: {},
26 | code: code,
27 | msg: msg,
28 | }
29 | this.body = resultJson;
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/database/migrations/20181216033631-init-comments.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | // 在执行数据库升级时调用的函数,创建 t_comments 表
5 | up: async (queryInterface, Sequelize) => {
6 | const { INTEGER, DATE, STRING } = Sequelize;
7 | await queryInterface.createTable('t_comments', {
8 | id: { type: INTEGER, primaryKey: true, autoIncrement: true },
9 | article_id: INTEGER,
10 | user_id: INTEGER,
11 | user_name: STRING,
12 | user_header_url: STRING,
13 | content: STRING(600),
14 | love_num: INTEGER,
15 | created_at: DATE,
16 | updated_at: DATE,
17 | });
18 | },
19 | // 在执行数据库降级时调用的函数,删除 t_comments 表
20 | down: async queryInterface => {
21 | await queryInterface.dropTable('t_comments');
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/database/migrations/20181216033658-init-comment-replys.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | // 在执行数据库升级时调用的函数,创建 t_comment_replys 表
5 | up: async (queryInterface, Sequelize) => {
6 | const { INTEGER, DATE, STRING } = Sequelize;
7 | await queryInterface.createTable('t_comment_replys', {
8 | id: { type: INTEGER, primaryKey: true, autoIncrement: true },
9 | comment_id: INTEGER,
10 | reply_id: INTEGER,
11 | from_user_id: INTEGER,
12 | from_user_name: STRING,
13 | to_user_id: INTEGER,
14 | to_user_name: STRING,
15 | content: STRING(600),
16 | created_at: DATE,
17 | updated_at: DATE,
18 | });
19 | },
20 | // 在执行数据库降级时调用的函数,删除 t_comment_replys 表
21 | down: async queryInterface => {
22 | await queryInterface.dropTable('t_comment_replys');
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/config/config.default.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = appInfo => {
4 | return {
5 | keys: appInfo.name + '_153332185447_3632',
6 | cluster: {
7 | listen: {
8 | path: '',
9 | port: 7002,
10 | hostname: '127.0.0.1',
11 | }
12 | },
13 | sequelize: {
14 | dialect: 'mysql', // support: mysql, mariadb, postgres, mssql
15 | database: '',
16 | host: '',
17 | username: '',
18 | password: '',
19 | port: 3306,
20 | },
21 | user: { // 初始化管理员的账号
22 | userName: 'admin',
23 | password: 'admin',
24 | },
25 | session: {
26 | maxAge: 3600 * 1000,
27 | },
28 | jwt: {
29 | cert: 'huanggegehaoshuai' // jwt秘钥
30 | },
31 | qiniu: { // 这里填写你七牛的Access Key和Secret Key
32 | ak: '',
33 | sk: ''
34 | }
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/database/migrations/20181213062911-init-articles-back.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | // 在执行数据库升级时调用的函数,创建 t_article_back 表
5 | up: async (queryInterface, Sequelize) => {
6 | const { INTEGER, DATE, STRING } = Sequelize;
7 | await queryInterface.createTable('t_article_backs', {
8 | id: { type: INTEGER, primaryKey: true, autoIncrement: true },
9 | article_id: INTEGER,
10 | type_id: INTEGER,
11 | title: STRING(255),
12 | text: STRING(255),
13 | images: STRING(1500), // 最多保存5张图片
14 | content: STRING(10000),
15 | article_num: INTEGER,
16 | status: INTEGER,
17 | created_at: DATE,
18 | updated_at: DATE,
19 | });
20 | },
21 | // 在执行数据库降级时调用的函数,删除 t_article_back 表
22 | down: async queryInterface => {
23 | await queryInterface.dropTable('t_article_backs');
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/database/migrations/20181129051931-init-users.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | // 在执行数据库升级时调用的函数,创建 users 表
5 | up: async (queryInterface, Sequelize) => {
6 | const { INTEGER, DATE, STRING } = Sequelize;
7 | await queryInterface.createTable('t_users', {
8 | id: { type: INTEGER, primaryKey: true, autoIncrement: true },
9 | username: STRING(30),
10 | password: STRING(255),
11 | header_url: STRING(255),
12 | user_email: STRING(100),
13 | phone: STRING(11),
14 | phone_validate: INTEGER(2),
15 | edit_type: INTEGER(2),
16 | language: INTEGER(2),
17 | receive_msg: INTEGER(2),
18 | notice: INTEGER(2),
19 | created_at: DATE,
20 | updated_at: DATE,
21 | });
22 | },
23 | // 在执行数据库降级时调用的函数,删除 users 表
24 | down: async queryInterface => {
25 | await queryInterface.dropTable('t_users');
26 | },
27 | };
28 |
--------------------------------------------------------------------------------
/app/controller/page.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 | const fs = require('fs')
5 | const util = require('util')
6 | const path = require('path')
7 | const readFilePromise = util.promisify(fs.readFile)
8 | class PageController extends Controller {
9 | /**
10 | * 打包后可以通过
11 | * http://rootUrl/ 和 http://rootUrl/admin 来访问前台可后台
12 | * 在开发阶段直接通过webpack的devserver来访问,接口通过proxyTable来代理
13 | */
14 | async index() {
15 | const { ctx } = this
16 | ctx.response.type = 'html'
17 | let page = await readFilePromise(path.resolve(__dirname, '../public/client/dist/index.html'))
18 | ctx.body = page
19 | }
20 | async admin() {
21 | const { ctx } = this
22 | ctx.response.type = 'html'
23 | let page = await readFilePromise(path.resolve(__dirname, '../public/admin/dist/index.html'))
24 | ctx.body = page
25 | }
26 | }
27 |
28 | module.exports = PageController;
29 |
30 |
--------------------------------------------------------------------------------
/database/migrations/20181130061343-init-articles.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | // 在执行数据库升级时调用的函数,创建 t_article 表
5 | up: async (queryInterface, Sequelize) => {
6 | const { INTEGER, DATE, STRING } = Sequelize;
7 | await queryInterface.createTable('t_articles', {
8 | id: { type: INTEGER, primaryKey: true, autoIncrement: true },
9 | title: STRING(255),
10 | text: STRING(255),
11 | images: STRING(1500), // 最多保存5张图片
12 | content: STRING(10000),
13 | user_id: INTEGER,
14 | type_id: INTEGER,
15 | article_num: INTEGER(2),
16 | ready_num: INTEGER(2),
17 | like_num: INTEGER(2),
18 | comment_num: INTEGER(2),
19 | status: INTEGER(2),
20 | created_at: DATE,
21 | updated_at: DATE,
22 | });
23 | },
24 | // 在执行数据库降级时调用的函数,删除 t_article 表
25 | down: async queryInterface => {
26 | await queryInterface.dropTable('t_articles');
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/app/model/articleTypes.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const { STRING, INTEGER, DATE } = app.Sequelize;
5 |
6 | const ArticleTypes = app.model.define('t_article_types', {
7 | id: { type: INTEGER, primaryKey: true, autoIncrement: true },
8 | type_name: STRING(255),
9 | user_id: INTEGER,
10 | delete: INTEGER(2),
11 | created_at: DATE,
12 | updated_at: DATE,
13 | });
14 |
15 | ArticleTypes.findOneByElement = async function(params) {
16 | if (typeof params !== 'object') {
17 | return {};
18 | }
19 | let data = await this.findOne({
20 | where: params,
21 | })
22 | if (data === null) {
23 | return data;
24 | }
25 | return data.dataValues;
26 |
27 | };
28 | ArticleTypes.findColumnByElement = async function(Column, params) {
29 | if (typeof params !== 'object') {
30 | return {};
31 | }
32 | return await this.findOne({
33 | where: params,
34 | });
35 |
36 | };
37 | return ArticleTypes;
38 | };
39 |
--------------------------------------------------------------------------------
/app/model/commentLove.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const { INTEGER, DATE } = app.Sequelize;
5 |
6 | // 第一个是别名 第二个是表名
7 | const CommentLove = app.model.define('t_comment_loves', {
8 | id: { type: INTEGER, primaryKey: true, autoIncrement: true },
9 | comment_id: INTEGER,
10 | user_id: INTEGER,
11 | created_at: DATE,
12 | updated_at: DATE
13 | }, {
14 | freezeTableName: true,
15 | tableName: 't_comment_loves'
16 | });
17 |
18 | CommentLove.findOneByElement = async function(params) {
19 | if (typeof params !== 'object') {
20 | return {};
21 | }
22 | let data = await this.findOne({
23 | where: params,
24 | })
25 | if (data === null) {
26 | return data;
27 | }
28 | return data.dataValues;
29 |
30 | };
31 | CommentLove.findColumnByElement = async function(Column, params) {
32 | if (typeof params !== 'object') {
33 | return {};
34 | }
35 | return await this.findOne({
36 | where: params,
37 | });
38 |
39 | };
40 | return CommentLove;
41 | };
42 |
--------------------------------------------------------------------------------
/app/model/articleBack.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const { STRING, INTEGER, DATE } = app.Sequelize;
5 |
6 | // 添加文章备份表
7 | const ArticleBack = app.model.define('t_article_backs', {
8 | id: { type: INTEGER, primaryKey: true, autoIncrement: true },
9 | article_id: INTEGER,
10 | type_id: INTEGER,
11 | title: STRING(255),
12 | text: STRING(255),
13 | images: STRING(1500), // 最多保存5张图片
14 | content: STRING(10000),
15 | article_num: INTEGER,
16 | status: INTEGER,
17 | created_at: DATE,
18 | updated_at: DATE,
19 | });
20 |
21 | ArticleBack.findOneByElement = async function(params) {
22 | if (typeof params !== 'object') {
23 | return {};
24 | }
25 | let data = await this.findOne({
26 | where: params,
27 | })
28 | if (data === null) {
29 | return data;
30 | }
31 | return data.dataValues;
32 |
33 | };
34 | ArticleBack.findColumnByElement = async function(Column, params) {
35 | if (typeof params !== 'object') {
36 | return {};
37 | }
38 | return await this.findOne({
39 | where: params,
40 | });
41 |
42 | };
43 | return ArticleBack;
44 | };
45 |
--------------------------------------------------------------------------------
/app/model/users.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const { STRING, INTEGER, DATE } = app.Sequelize;
5 |
6 | const Users = app.model.define('t_users', {
7 | id: { type: INTEGER, primaryKey: true, autoIncrement: true },
8 | username: STRING(30),
9 | password: STRING(255),
10 | header_url: STRING(255),
11 | user_email: STRING(100),
12 | phone: STRING(11),
13 | phone_validate: INTEGER(2),
14 | edit_type: INTEGER(2),
15 | language: INTEGER(2),
16 | receive_msg: INTEGER(2),
17 | notice: INTEGER(2),
18 | created_at: DATE,
19 | updated_at: DATE,
20 | });
21 |
22 | Users.findOneByElement = async function(params) {
23 | if (typeof params !== 'object') {
24 | return {};
25 | }
26 | let data = await this.findOne({
27 | where: params,
28 | })
29 | if (data === null) {
30 | return data;
31 | }
32 | return data.dataValues;
33 |
34 | };
35 | Users.findColumnByElement = async function(Column, params) {
36 | if (typeof params !== 'object') {
37 | return {};
38 | }
39 | return await this.findOne({
40 | where: params,
41 | });
42 |
43 | };
44 | return Users;
45 | };
46 |
--------------------------------------------------------------------------------
/app/model/commentReply.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const { STRING, INTEGER, DATE } = app.Sequelize;
5 |
6 | // 第一个是别名 第二个是表名
7 | const CommentReply = app.model.define('t_comment_replys', {
8 | id: { type: INTEGER, primaryKey: true, autoIncrement: true },
9 | comment_id: INTEGER,
10 | reply_id: INTEGER,
11 | from_user_id: INTEGER,
12 | from_user_name: STRING,
13 | to_user_id: INTEGER,
14 | to_user_name: STRING,
15 | content: STRING(600),
16 | created_at: DATE,
17 | updated_at: DATE,
18 | }, {
19 | freezeTableName: true,
20 | tableName: 't_comment_replys'
21 | });
22 |
23 | CommentReply.findOneByElement = async function(params) {
24 | if (typeof params !== 'object') {
25 | return {};
26 | }
27 | let data = await this.findOne({
28 | where: params,
29 | })
30 | if (data === null) {
31 | return data;
32 | }
33 | return data.dataValues;
34 |
35 | };
36 | CommentReply.findColumnByElement = async function(Column, params) {
37 | if (typeof params !== 'object') {
38 | return {};
39 | }
40 | return await this.findOne({
41 | where: params,
42 | });
43 |
44 | };
45 | return CommentReply;
46 | };
47 |
--------------------------------------------------------------------------------
/app/controller/client/login.js:
--------------------------------------------------------------------------------
1 | const Controller = require('egg').Controller
2 | class LoginController extends Controller {
3 | // 获取验证码
4 | async getCaptcha() {
5 | const { ctx } = this
6 | const captcha = ctx.service.login.genCaptcha()
7 | // 把生成的验证码文本信息(如:t8ec),存入session,以待验证
8 | ctx.session.code = captcha.text
9 | // 将生成的验证码svg图片返回给前端
10 | ctx.body = captcha.data
11 | }
12 | // 登录
13 | async login() {
14 | const { ctx } = this
15 | const { username, password } = ctx.request.body
16 | // let isCaptchaVali = ctx.service.login.checkCaptcha(code)
17 | // if (!isCaptchaVali) {
18 | // ctx.error(1, '验证码不正确')
19 | // return
20 | // }
21 | // 验证码正确则继续登录操作
22 | const userData = await ctx.service.login.login({ username, password })
23 | if (userData.err === 1 || userData.err === 2) {
24 | ctx.error(2, '用户名不存在或密码错误')
25 | return
26 | }
27 | let data = {
28 | username: userData.user.username,
29 | uid: userData.user.id,
30 | header_url: userData.user.header_url,
31 | token: userData.token
32 | }
33 | ctx.success(data)
34 | }
35 | // 注册
36 | async register() {
37 | const { ctx } = this
38 | // 暂时未开发
39 | ctx.success({})
40 | }
41 | }
42 |
43 | module.exports = LoginController
44 |
--------------------------------------------------------------------------------
/app/model/comment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const { STRING, INTEGER, DATE } = app.Sequelize;
5 |
6 | // 第一个是别名 第二个是表名
7 | const Comment = app.model.define('t_comments', {
8 | id: { type: INTEGER, primaryKey: true, autoIncrement: true },
9 | article_id: INTEGER,
10 | user_id: INTEGER,
11 | user_name: STRING,
12 | user_header_url: STRING,
13 | content: STRING(600),
14 | love_num: INTEGER,
15 | created_at: DATE,
16 | updated_at: DATE,
17 | }, {
18 | freezeTableName: true,
19 | tableName: 't_comments'
20 | });
21 |
22 | Comment.findOneByElement = async function(params) {
23 | if (typeof params !== 'object') {
24 | return {};
25 | }
26 | let data = await this.findOne({
27 | where: params,
28 | })
29 | if (data === null) {
30 | return data;
31 | }
32 | return data.dataValues;
33 |
34 | };
35 | Comment.findColumnByElement = async function(Column, params) {
36 | if (typeof params !== 'object') {
37 | return {};
38 | }
39 | return await this.findOne({
40 | where: params,
41 | });
42 |
43 | };
44 | Comment.associate = function() {
45 | app.model.Comment.hasMany(app.model.CommentReply, { foreignKey: 'comment_id', targetKey: 'id' });
46 | };
47 | return Comment;
48 | };
49 |
--------------------------------------------------------------------------------
/app/controller/users.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Controller = require('egg').Controller;
4 |
5 | class UserController extends Controller {
6 | async index() {
7 | const ctx = this.ctx;
8 | const query = {
9 | limit: ctx.helper.parseInt(ctx.query.limit),
10 | offset: ctx.helper.parseInt(ctx.query.offset),
11 | };
12 | ctx.success(await ctx.service.user.list(query))
13 | }
14 | async test() {
15 | const ctx = this.ctx;
16 | let username = 'jyf1',
17 | password = '123';
18 | ctx.success(await ctx.service.login.login({ username, password }))
19 | }
20 |
21 | async show() {
22 | const ctx = this.ctx;
23 | ctx.body = await ctx.service.user.find(ctx.helper.parseInt(ctx.params.id));
24 | }
25 |
26 | async create() {
27 | const ctx = this.ctx;
28 | const user = await ctx.service.user.create(ctx.request.body);
29 | ctx.status = 201;
30 | ctx.body = user;
31 | }
32 |
33 | async update() {
34 | const ctx = this.ctx;
35 | const id = ctx.helper.parseInt(ctx.params.id);
36 | const body = ctx.request.body;
37 | ctx.body = await ctx.service.user.update({ id, updates: body });
38 | }
39 |
40 | async destroy() {
41 | const ctx = this.ctx;
42 | const id = ctx.helper.parseInt(ctx.params.id);
43 | await ctx.service.user.del(id);
44 | ctx.status = 200;
45 | }
46 | }
47 |
48 | module.exports = UserController;
49 |
--------------------------------------------------------------------------------
/app/model/article.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = app => {
4 | const { STRING, INTEGER, DATE } = app.Sequelize;
5 |
6 | // 第一个是别名 第二个是表名
7 | const Article = app.model.define('t_articles', {
8 | id: { type: INTEGER, primaryKey: true, autoIncrement: true },
9 | title: STRING(255),
10 | text: STRING(255),
11 | images: STRING(1500), // 最多保存5张图片
12 | content: STRING(10000),
13 | user_id: INTEGER,
14 | type_id: INTEGER,
15 | article_num: INTEGER(2),
16 | ready_num: INTEGER(2),
17 | like_num: INTEGER(2),
18 | comment_num: INTEGER(2),
19 | status: INTEGER(2), // 0 发布 1 草稿 2 私密 3 删除
20 | created_at: DATE,
21 | updated_at: DATE
22 | }, {
23 | freezeTableName: true,
24 | tableName: 't_articles'
25 | });
26 |
27 | Article.findOneByElement = async function(params) {
28 | if (typeof params !== 'object') {
29 | return {};
30 | }
31 | let data = await this.findOne({
32 | where: params,
33 | })
34 | if (data === null) {
35 | return data;
36 | }
37 | return data.dataValues;
38 |
39 | };
40 | Article.findColumnByElement = async function(Column, params) {
41 | if (typeof params !== 'object') {
42 | return {};
43 | }
44 | return await this.findOne({
45 | where: params,
46 | });
47 |
48 | };
49 | Article.associate = function() {
50 | app.model.Article.belongsTo(app.model.Users, { foreignKey: 'user_id', targetKey: 'id' });
51 | };
52 | return Article;
53 | };
54 |
--------------------------------------------------------------------------------
/app/service/login.js:
--------------------------------------------------------------------------------
1 | const Service = require('egg').Service;
2 | const jwt = require('jsonwebtoken')
3 | const svgCaptcha = require('svg-captcha')
4 | class LoginService extends Service {
5 | // 生成验证码
6 | genCaptcha() {
7 | return svgCaptcha.create({
8 | width: 85,
9 | height: 38
10 | })
11 | }
12 | // 检查验证码是否正确
13 | checkCaptcha(code) {
14 | const { ctx } = this
15 | code = code.toLowerCase()
16 | let sessCode = ctx.session.code ? ctx.session.code.toLowerCase() : null // 拿到之前存在session中的验证码
17 | // 进行验证
18 | if (code === sessCode) {
19 | // 成功后验证码作废
20 | ctx.session.code = null
21 | }
22 | return code === sessCode
23 | }
24 | // 登录操作
25 | async login({ username, password }) {
26 | const { ctx, app } = this
27 | const userData = await ctx.model.Users.findOneByElement({
28 | username: username
29 | })
30 | let user = {
31 | user: {},
32 | token: '',
33 | err: 0 // 0 登录成功 1 用户不存在 2 用户密码错误
34 | }
35 | // 用户不存在
36 | if (!userData) {
37 | user.err = 1;
38 | return user;
39 | }
40 | if (userData.password !== password) {
41 | user.err = 2;
42 | return user;
43 | }
44 |
45 | // 找到则以用户id 生成token
46 | const token = jwt.sign({
47 | id: userData.id
48 | }, app.config.jwt.cert, {
49 | expiresIn: '10h' // token过期时间
50 | })
51 | user.user = userData;
52 | user.token = token;
53 | return user;
54 | }
55 | }
56 |
57 | module.exports = LoginService
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
3 | "version": "1.0.0",
4 | "description": "",
5 | "private": true,
6 | "dependencies": {
7 | "cheerio": "^1.0.0-rc.2",
8 | "egg": "^2.2.1",
9 | "egg-mongoose": "^3.1.0",
10 | "egg-scripts": "^2.11.0",
11 | "egg-sequelize": "^4.2.0",
12 | "jsonwebtoken": "^8.3.0",
13 | "mysql2": "^1.6.4",
14 | "qiniu": "^7.2.1",
15 | "svg-captcha": "^1.3.11"
16 | },
17 | "devDependencies": {
18 | "autod": "^3.0.1",
19 | "autod-egg": "^1.0.0",
20 | "egg-bin": "^4.3.5",
21 | "egg-ci": "^1.8.0",
22 | "egg-mock": "^3.14.0",
23 | "eslint": "^4.11.0",
24 | "eslint-config-egg": "^6.0.0",
25 | "factory-girl": "^5.0.2",
26 | "lodash": "^4.17.10",
27 | "sequelize-cli": "^4.1.1",
28 | "webstorm-disable-index": "^1.2.0"
29 | },
30 | "engines": {
31 | "node": ">=8.9.0"
32 | },
33 | "scripts": {
34 | "start": "egg-scripts start --daemon --title=egg-server-demo",
35 | "stop": "egg-scripts stop --title=egg-server-demo",
36 | "dev": "egg-bin dev",
37 | "debug": "egg-bin debug",
38 | "test": "npm run lint -- --fix && npm run test-local",
39 | "test-local": "egg-bin test",
40 | "cov": "egg-bin cov",
41 | "lint": "eslint .",
42 | "ci": "npm run lint && npm run cov",
43 | "autod": "autod",
44 | "sequelize": "sequelize --"
45 | },
46 | "ci": {
47 | "version": "8"
48 | },
49 | "repository": {
50 | "type": "git",
51 | "url": ""
52 | },
53 | "author": "wade",
54 | "license": "MIT"
55 | }
56 |
--------------------------------------------------------------------------------
/app/controller/articleType.js:
--------------------------------------------------------------------------------
1 | const Controller = require('egg').Controller
2 | class ArticleTypeController extends Controller {
3 | // 获取所有文章列表
4 | async getListByUserId() {
5 | const { ctx } = this
6 | let data = await ctx.service.articleType.list()
7 | ctx.success(data)
8 | }
9 | // 添加新文集
10 | async create() {
11 | const ctx = this.ctx;
12 | const { typeName } = ctx.request.body
13 | let query = {
14 | userId: ctx.userId,
15 | typeName: typeName
16 | }
17 | const articleType = await ctx.service.articleType.create(query);
18 | if (articleType.code === 0) {
19 | ctx.success(articleType.data)
20 | } else {
21 | ctx.error(articleType.code, articleType.msg)
22 | }
23 | }
24 |
25 | // 删除文集
26 | async del() {
27 | const ctx = this.ctx;
28 | const { id } = ctx.request.body
29 | const articleType = await ctx.service.articleType.del(id);
30 | if (articleType.code === 0) {
31 | ctx.success(articleType.data)
32 | } else {
33 | ctx.error(articleType.code, articleType.msg)
34 | }
35 | }
36 |
37 | // 修改文集
38 | async update() {
39 | const ctx = this.ctx;
40 | const { id, typeName } = ctx.request.body
41 | if (id == null || typeName.length === 0) {
42 | ctx.error(1, '文集名称不能为空')
43 | }
44 | let query = {
45 | id: id,
46 | type_name: typeName
47 | }
48 | const articleType = await ctx.service.articleType.update(query);
49 | if (articleType.code === 0) {
50 | ctx.success(articleType.data)
51 | } else {
52 | ctx.error(articleType.code, articleType.msg)
53 | }
54 | }
55 |
56 | }
57 |
58 | module.exports = ArticleTypeController
59 |
--------------------------------------------------------------------------------
/app/service/articleType.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Service = require('egg').Service;
4 |
5 | class ArticleType extends Service {
6 | async list() {
7 | let user_id = this.ctx.userId;
8 | return this.ctx.model.ArticleTypes.findAndCountAll({
9 | where: { user_id: user_id },
10 | order: [[ 'created_at', 'asc' ]],
11 | });
12 | }
13 |
14 | async find(id) {
15 | const articleTypes = await this.ctx.model.ArticleTypes.findById(id);
16 | if (!articleTypes) {
17 | this.ctx.error(404, '不存在文集类型');
18 | }
19 | return articleTypes;
20 | }
21 |
22 | async create(query) {
23 | const { ctx } = this;
24 | let option = {
25 | type_name: query.typeName,
26 | user_id: query.userId,
27 | delete: '0'
28 | }
29 | let typeData = await ctx.model.ArticleTypes.findOneByElement(option)
30 | if (typeData) {
31 | return {
32 | code: 1,
33 | msg: '不能创建同一个文集名称'
34 | }
35 | }
36 | let data = await ctx.model.ArticleTypes.create(option);
37 | if (!data) {
38 | return {
39 | code: 1,
40 | msg: '插入失败'
41 | }
42 | }
43 | return {
44 | code: 0,
45 | data: data.dataValues
46 | }
47 |
48 | }
49 |
50 | async update(query) {
51 | const articleTypes = await this.ctx.model.ArticleTypes.findById(query.id);
52 | if (!articleTypes) {
53 | return {
54 | code: 404,
55 | msg: '不存在文集类型'
56 | }
57 | }
58 | let user_id = this.ctx.userId
59 | const articleTypes2 = await this.ctx.model.ArticleTypes.findAndCountAll({ where: { type_name: query.type_name ,user_id: user_id} });
60 | if (articleTypes2.count > 0) {
61 | return {
62 | code: 1,
63 | msg: '已存在该文集类型'
64 | }
65 | }
66 | let data = await articleTypes.update(query);
67 | return {
68 | code: 0,
69 | data: data
70 | }
71 | }
72 |
73 | async del(id) {
74 | const articleTypes = await this.ctx.model.ArticleTypes.findById(id);
75 | if (!articleTypes) {
76 | return {
77 | code: 404,
78 | msg: '不存在文集类型'
79 | }
80 | }
81 | let data = await articleTypes.destroy();
82 | return {
83 | code: 0,
84 | data: data.dataValues
85 | }
86 | }
87 | }
88 |
89 | module.exports = ArticleType;
90 |
--------------------------------------------------------------------------------
/app/router.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * @param {Egg.Application} app - egg application
5 | */
6 | module.exports = app => {
7 | const { router, controller } = app;
8 | // 后台授权中间件
9 | const auth = app.middleware.auth()
10 | // 页面跳转
11 | router.get('/', controller.page.index);
12 | // 获取验证码
13 | router.get('/getCaptcha', controller.client.login.getCaptcha);
14 | router.get('/admin/getCaptcha', controller.admin.login.getCaptcha);
15 | // 登录
16 | router.post('/login', controller.client.login.login);
17 | // 获取七牛token
18 | router.get('/getQiniuToken', auth, controller.article.getQiniuToken);
19 |
20 | // 通过用户Id获取文集类型
21 | router.get('/getListByUserId', auth, controller.articleType.getListByUserId);
22 | // 通过用户Id获取文集类型
23 | router.post('/addType', auth, controller.articleType.create);
24 | // 通过Id修改文集类型
25 | router.post('/updateType', auth, controller.articleType.update);
26 | // 通过Id删除文集类型
27 | router.post('/delType', auth, controller.articleType.del);
28 |
29 | // 获取文章列表
30 | router.get('/getArticleList', controller.article.getArticleList);
31 | // 通过type_id 获取文章列表
32 | router.get('/getArticleListByTypes', controller.article.getArticleListByTypes);
33 | // 通过type_id 获取文章列表+备份
34 | router.get('/getArticleBackByTypeId', controller.article.getArticleBackByTypeId);
35 | // 通过backId 获取备份表
36 | router.get('/getArticleBackById', controller.article.getArticleBackById);
37 | // 通过id 获取文章列表
38 | router.get('/getArticleById', controller.article.getArticleById);
39 | // 通过id 获取文章详情
40 | router.get('/getArticleDetailById', controller.article.getArticleDetailById);
41 | // 通过type_id 添加文章
42 | router.post('/addArticleByTypeId', auth, controller.article.addArticleByTypeId);
43 | // 修改文章状态
44 | router.post('/updateStatus', auth, controller.article.updateStatus);
45 | // 修改文章
46 | router.post('/updateArticle', auth, controller.article.update);
47 | // 更新修改文章
48 | router.post('/editArticle', auth, controller.article.backEdit);
49 |
50 | // 获取评论列表
51 | router.get('/getCommentListByArticleId', controller.article.getCommentListByArticleId);
52 | // 添加评论
53 | router.post('/addComment', auth, controller.article.addComment);
54 | // 添加评论回复
55 | router.post('/addCommentReply', auth, controller.article.addCommentReply);
56 | // 评论点赞
57 | router.post('/addCommentLoveNum', auth, controller.article.addCommentLoveNum);
58 |
59 | router.get('/users', controller.users.index);
60 | router.get('/test', controller.users.test);
61 |
62 | };
63 |
--------------------------------------------------------------------------------
/database/create.md:
--------------------------------------------------------------------------------
1 | ## MAC mysql 安装
2 | ```
3 | brew install mysql
4 | brew service start mysql
5 | ```
6 |
7 | ## 初始化项目
8 | ```
9 | // 初始化
10 |
11 | egg-init --type=simple --dir=sequelize-project
12 | cd sequelize-project
13 | npm i
14 |
15 | // 安装
16 |
17 | npm install --save egg-sequelize mysql2
18 |
19 | // config/plugin.js 配置
20 |
21 | exports.sequelize = {
22 | enable: true,
23 | package: 'egg-sequelize',
24 | };
25 |
26 | // config/config.default.js配置
27 |
28 | config.sequelize = {
29 | dialect: 'mysql',
30 | host: '127.0.0.1',
31 | port: 3306,
32 | database: 'egg-sequelize-doc-default',
33 | username: 'root',
34 | password: 'root'
35 | };
36 | ```
37 |
38 | ## mysql 创建表
39 | ```
40 | mysql -u root -e 'CREATE DATABASE IF NOT EXISTS `egg-sequelize-doc-default`;'
41 | ```
42 | ## 安装 sequelize-cli
43 | ```
44 | cnpm i sequelize-cli -D
45 | ```
46 | ## 项目根目录创建 `.sequelizerc`文件
47 |
48 | ```
49 | 'use strict';
50 |
51 | const path = require('path');
52 |
53 | module.exports = {
54 | config: path.join(__dirname, 'database/config.json'),
55 | 'migrations-path': path.join(__dirname, 'database/migrations'),
56 | 'seeders-path': path.join(__dirname, 'database/seeders'),
57 | 'models-path': path.join(__dirname, 'app/model'),
58 | };
59 | ```
60 |
61 | ## 初始化 Migrations 配置文件和目录
62 |
63 | ```
64 | npx sequelize init:config
65 | npx sequelize init:migrations
66 | ```
67 |
68 | ## 在database/migrations创建表文件
69 | ```
70 | npx sequelize migration:generate --name=init-users(users表名字)
71 | ```
72 |
73 | ## 修改init-users文件
74 | ```
75 | 'use strict';
76 |
77 | module.exports = {
78 | // 在执行数据库升级时调用的函数,创建 users 表
79 | up: async (queryInterface, Sequelize) => {
80 | const { INTEGER, DATE, STRING } = Sequelize;
81 | await queryInterface.createTable('users', {
82 | id: { type: INTEGER, primaryKey: true, autoIncrement: true },
83 | name: STRING(30),
84 | age: INTEGER,
85 | created_at: DATE,
86 | updated_at: DATE,
87 | });
88 | },
89 | // 在执行数据库降级时调用的函数,删除 users 表
90 | down: async queryInterface => {
91 | await queryInterface.dropTable('users');
92 | },
93 | };
94 | ```
95 |
96 | ## 执行 migrate 进行数据库变更
97 | ```
98 | # 升级数据库
99 | npx sequelize db:migrate
100 | # 如果有问题需要回滚,可以通过 `db:migrate:undo` 回退一个变更
101 | # npx sequelize db:migrate:undo
102 | # 可以通过 `db:migrate:undo:all` 回退到初始状态
103 | # npx sequelize db:migrate:undo:all
104 | ```
105 |
106 | [官方文档链接](https://eggjs.org/zh-cn/tutorials/sequelize.html)
107 |
--------------------------------------------------------------------------------
/app/service/comment.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Service = require('egg').Service;
4 |
5 | class Comment extends Service {
6 | // 通过文章id获取评论列表
7 | async getCommentList({ article_id, user_id, offset = 0, limit = 10, order = 'desc' }) {
8 | const { ctx } = this
9 | const options = {
10 | offset,
11 | limit,
12 | order: [[ 'created_at', order ], [ 'id', order ]],
13 | where: { article_id },
14 | include: [{
15 | order: [[ 'created_at', 'asc' ], [ 'id', 'asc' ]],
16 | model: this.ctx.model.CommentReply
17 | }]
18 | };
19 | let data = await ctx.model.Comment.findAndCountAll(options)
20 | let rows = []
21 | for (let info of data.rows) {
22 | let lovesId = await ctx.model.CommentLove.findOne({ attributes: [ 'id' ], where: { user_id: user_id, comment_id: info.id } })
23 | info.dataValues.loveId = lovesId
24 | rows.push(info)
25 | }
26 | return {
27 | rows: rows
28 | }
29 | }
30 | // 添加评论
31 | async create(query) {
32 | const { ctx } = this;
33 | let user = await ctx.model.Users.findById(query.user_id);
34 | if (!user) {
35 | return {
36 | code: 401,
37 | msg: '用户不存在'
38 | }
39 | }
40 | query.user_name = user.username
41 | query.user_header_url = user.header_url
42 | query.love_num = 0
43 | query.created_at = new Date()
44 | query.updated_at = new Date()
45 | let data = await ctx.model.Comment.create(query);
46 | if (!data) {
47 | return {
48 | code: 401,
49 | msg: '评论失败'
50 | }
51 | }
52 | await ctx.service.article.addCommentNum(data.article_id)
53 | return {
54 | code: 0,
55 | data: data.dataValues
56 | }
57 | }
58 | // 添加评论回复
59 | async createReply(query) {
60 | const { ctx } = this;
61 | let user = await ctx.model.Users.findById(query.from_user_id);
62 | if (!user) {
63 | return {
64 | code: 401,
65 | msg: '用户不存在'
66 | }
67 | }
68 | query.from_user_name = user.username
69 | query.created_at = new Date()
70 | query.updated_at = new Date()
71 | let data = await ctx.model.CommentReply.create(query);
72 | if (!data) {
73 | return {
74 | code: 1,
75 | msg: '回复失败'
76 | }
77 | }
78 | return {
79 | code: 0,
80 | data: data.dataValues
81 | }
82 | }
83 | // 添加或者取消评论点赞
84 | async LoveNum(id, loveId) {
85 | let num = 0
86 | let option = {
87 | where: {
88 | user_id: this.ctx.userId,
89 | comment_id: id,
90 | }
91 | }
92 | if (loveId) {
93 | option.where.id = loveId
94 | }
95 | let Loves = await this.ctx.model.CommentLove.findOne(option);
96 | if (Loves) {
97 | await Loves.destroy();
98 | num = -1
99 | } else {
100 | let op = {
101 | comment_id: id,
102 | user_id: this.ctx.userId,
103 | created_at: new Date()
104 | }
105 | Loves = await this.ctx.model.CommentLove.create(op)
106 | num = 1
107 | }
108 | await this.app.model.query('update t_comments set love_num = love_num+:num where id=:id', { type: 'UPDATE', replacements: { id: id, num: num } }).then(results => results);
109 | Loves.dataValues.num = num
110 | return Loves
111 | }
112 | }
113 |
114 | module.exports = Comment;
115 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 仿简书后端接口平台
2 |
3 | 这个是一个基于egg模仿简书的后端接口,主要是为了学习使用,目前还在迭代开发中
4 |
5 | ## 目前规划
6 |
7 | - 后端 egg.js (已完成部分功能)[源码链接](https://github.com/jieyuanfei/jianshu-egg-api)
8 | - 后台管理 (计划中)[源码链接](https://github.com/jieyuanfei/jianshu-egg-api)
9 | - pc前端 vue (已完成部分功能)[源码链接](https://github.com/jieyuanfei/jianshu-vue-pc)
10 | - 移动端APP react native (已完成部分功能)[源码链接](https://github.com/jieyuanfei/jianshu-react-mobile)
11 | - 小程序 mp-vue (计划中)
12 |
13 | ## 快速入门
14 |
15 | ### 技术栈
16 |
17 | - 后端: [egg.js](https://eggjs.org/zh-cn/)、[mysql](http://www.runoob.com/mysql/mysql-tutorial.html)
18 |
19 | - 移动端APP: [react native](https://reactnative.cn/)
20 |
21 | - 小程序: [mp-vue](https://reactnative.cn/)
22 |
23 | - 前端:
24 | - PC端:[vue](https://cn.vuejs.org/)、[element](http://element-cn.eleme.io/#/zh-CN)
25 | - 后台管理端:[vue](https://cn.vuejs.org/)、[element](http://element-cn.eleme.io/#/zh-CN)
26 |
27 | ### 功能特性
28 |
29 | - 轻量级Markdown编辑器,图片上传七牛
30 | - 基本仿照简书所有的功能
31 | - 部分功能自己添加的(每周一篇博客提交日历提醒功能)
32 | - 标签云
33 | ### 线上地址
34 | [PC端页面展示](http://39.108.125.74/jianshu/#/)
35 | ### 图片演示
36 | #### PC前台 [源码链接](https://github.com/jieyuanfei/jianshu-vue-pc)
37 |
38 | - 首页
39 | 
40 | 
41 |
42 | - 文章详情页-评论回复-点赞-收藏
43 | 
44 |
45 | - 编译文章
46 | 
47 |
48 | - 关注
49 | 
50 |
51 | - 登录注册
52 | 
53 |
54 | - APP下载页面
55 | 
56 | ### 目录结构
57 |
58 | ```
59 | │ .autod.conf.js
60 | │ .eslintignore
61 | │ .eslintrc
62 | │ .gitignore
63 | │ .travis.yml
64 | │ app.js // 项目启动前执行,比如写入管理员
65 | │ appveyor.yml
66 | │ package.json
67 | │ README.md
68 | │
69 | ├─db
70 | │ │ db.md // db设计文档
71 | ├─screenshot // 演示图片路径
72 | ├─app
73 | │ │ router.js // 服务端路由
74 | │ │
75 | │ ├─controller
76 | │ │
77 | │ ├─extend
78 | │ │ helper.js
79 | │ │
80 | │ ├─middleware
81 | │ │ auth.js // 登录验证中间件
82 | │ │
83 | │ ├─model
84 | │ │
85 | │ │
86 | │ ├─public
87 | │ │
88 | │ └─service // service部分用来执行具体的操作
89 | │
90 | │
91 | ├─config
92 | │ config.default.js // 项目配置相关
93 | │ plugin.js
94 | │
95 | └─test // 测试相关
96 | └─app
97 | └─controller
98 | home.test.js
99 | ```
100 |
101 | ### 全局配置
102 |
103 | ```javascript
104 | module.exports = appInfo => {
105 | return {
106 | keys: appInfo.name + '_153332185447_3632',
107 | cluster: {
108 | listen: { // 启动监听的端口号配置
109 | path: '',
110 | port: 7002,
111 | hostname: '127.0.0.1',
112 | }
113 | },
114 | sequelize: {
115 | dialect: 'mysql', // support: mysql, mariadb, postgres, mssql
116 | database: '数据库名字',
117 | host: 'ip',
118 | username: '用户名',
119 | password: '密码',
120 | port: 3306,
121 | },
122 | user: { // 初始化管理员的账号
123 | userName: 'admin',
124 | password: 'admin',
125 | },
126 | session: {
127 | maxAge: 3600 * 1000,
128 | },
129 | jwt: {
130 | cert: 'huanggegehaoshuai' // jwt秘钥
131 | },
132 | qiniu: { // 这里填写你七牛的Access Key和Secret Key
133 | ak: '',
134 | sk: ''
135 | }
136 | }
137 | };
138 | ```
139 |
140 |
141 | ### 本地运行
142 |
143 | 安装mysql数据库和node环境。
144 |
145 | ``` bash
146 | # 安装服务端依赖
147 | npm install
148 | # 运行服务
149 | npm run dev
150 |
151 | ```
152 |
153 | ### 打包
154 |
155 | ```bash
156 | # 在前台和后台目录分别
157 | npm run build
158 | # 在项目根目录
159 | npm install --production
160 | # 启动
161 | npm start
162 | ```
163 |
--------------------------------------------------------------------------------
/db/db.md:
--------------------------------------------------------------------------------
1 | ## 简书数据库设计
2 |
3 | ### 用户基础表 t_user_base
4 |
5 | | 字段 | 类型 | 字段长度 | 说明 | 备注 |
6 | | :------: |:------:|:------: | :------: | :------: |
7 | | id | varchar | 30 | 表主键id | - |
8 | | header_url | varchar | 150 | 用户头像 | - |
9 | | user_name | varchar | 30 | 用户名称 | - |
10 | | user_email | varchar | 30 | 电子邮件 | - |
11 | | phone | varchar | 11 | 用户手机 | - |
12 | | phone_validate | int | 1 | 用户手机是否已经校验 | 0 已校验 / 1 未校验 |
13 | | edit_type | int | 1 | 编译文本类型 | 0 富文本 / 1 markdown |
14 | | language | int | 1 | 语言 | 0 简体中文 / 1 繁体 / 2 英文 |
15 | | receive_msg | int | 1 | 接收简讯 | 0 所有 / 1 我关注的 |
16 | | notice | int | 1 | 邮件通知 | 0 所有动态 / 1 每天未读汇总 / 2 不接收 |
17 | | create_time | date | 100 | 创建时间 | - |
18 |
19 | ### 个人资料表 t_user_data
20 |
21 | | 字段 | 类型 | 字段长度 | 说明 | 备注 |
22 | | :------: |:------:|:------: | :------: | :------: |
23 | | id | varchar | 30 | 表主键id | - |
24 | | user_id | varchar | 30 | 用户id | - |
25 | | sex | int | 1 | 性别 | 0 男 / 1 女 / 2 保密 |
26 | | introduction | varchar | 150 | 个人简介 | - |
27 | | web | varchar | 500 | 个人网站链接 | - |
28 | | wx_qrcode_url | varchar | 150 | 微信二维码 | - |
29 | | create_time | date | 100 | 创建时间 | - |
30 |
31 | ### 黑名单表 t_user_blacklist
32 |
33 | | 字段 | 类型 | 字段长度 | 说明 | 备注 |
34 | | :------: |:------:|:------: | :------: | :------: |
35 | | id | varchar | 30 | 表主键id | - |
36 | | user_id | varchar | 30 | 用户id | - |
37 | | black_user_id | varchar | 30 | 拉黑用户id | - |
38 | | create_time | date | 100 | 创建时间 | - |
39 |
40 | ### 赞赏表 t_user_appreciate
41 |
42 | | 字段 | 类型 | 字段长度 | 说明 | 备注 |
43 | | :------: |:------:|:------: | :------: | :------: |
44 | | id | varchar | 30 | 表主键id | - |
45 | | user_id | varchar | 30 | 用户id | - |
46 | | describe | varchar | 300 | 描述 | - |
47 | | status | int | 1 | 开启状态 | 0 关闭 / 1 开启 |
48 | | create_time | date | 100 | 创建时间 | - |
49 |
50 | ### 文章类型表/标签表 t_article_type
51 |
52 | | 字段 | 类型 | 字段长度 | 说明 | 备注 |
53 | | :------: |:------:|:------: | :------: | :------: |
54 | | id | varchar | 30 | 表主键id | - |
55 | | type_name | varchar | 30 | 类型名称 | - |
56 | | user_id | varchar | 30 | 用户id | - |
57 | | delete | int | 1 | 是否删除 | 0 正常/1 删除 |
58 | | create_time | date | 100 | 创建时间 | - |
59 |
60 | ### 文章表 t_article
61 |
62 | | 字段 | 类型 | 字段长度 | 说明 | 备注 |
63 | | :------: |:------:|:------: | :------: | :------: |
64 | | id | varchar | 30 | 表主键id | - |
65 | | title | varchar | 100 | 标题 | - |
66 | | content | varchar | 10000 | 内容 | - |
67 | | user_id | varchar | 30 | 用户id | - |
68 | | type_id | varchar | 30 | 类型id | - |
69 | | article_num | int | 100 | 文章字数 | - |
70 | | ready_num | int | 100 | 阅读数 | - |
71 | | like_num | int | 100 | 喜欢 | - |
72 | | comment_num | int | 100 | 评论数 | - |
73 | | edit_time | date | 100 | 文章编译时间 | - |
74 | | status | int | 1 | 文章状态 | 0 已发布/ 1 删除 /2 私密 |
75 |
76 | ### 文章备份表/草稿表/历史表 t_article_backup
77 |
78 | | 字段 | 类型 | 字段长度 | 说明 | 备注 |
79 | | :------: |:------:|:------: | :------: | :------: |
80 | | id | varchar | 30 | 表主键id | - |
81 | | article_id | varchar | 30 | 文章表id | - |
82 | | title | varchar | 100 | 标题 | - |
83 | | content | varchar | 10000 | 内容 | - |
84 | | user_id | varchar | 30 | 用户id | - |
85 | | type | int | 1 | 表类型 | 0 草稿 / 1 历史表 |
86 | | delete | int | 1 | 文章是否删除 | 0 正常 / 1 删除 |
87 | | edit_time | date | 100 | 文章编译时间 | - |
88 |
89 | ### 评论表 t_comment
90 |
91 | | 字段 | 类型 | 字段长度 | 说明 | 备注 |
92 | | :------: |:------:|:------: | :------: | :------: |
93 | | id | varchar | 30 | 表主键id | - |
94 | | article_id | varchar | 255 | 文章表id | - |
95 | | content | varchar | 10000 | 内容 | - |
96 | | user_id | varchar | 30 | 用户id | - |
97 | | edit_time | date | 100 | 编译时间 | - |
98 |
99 | ### 评论子表 t_comment_multi
100 |
101 | | 字段 | 类型 | 字段长度 | 说明 | 备注 |
102 | | :------: |:------:|:------: | :------: | :------: |
103 | | id | varchar | 30 | 表主键id | - |
104 | | comment_id | varchar | 30 | 文章表id | - |
105 | | content | varchar | 10000 | 内容 | - |
106 | | user_id | varchar | 30 | 用户id | - |
107 | | edit_time | date | 100 | 编译时间 | - |
108 |
109 | ### 收藏/喜欢表 t_collection
110 |
111 | | 字段 | 类型 | 字段长度 | 说明 | 备注 |
112 | | :------: |:------:|:------: | :------: | :------: |
113 | | id | varchar | 30 | 表主键id | - |
114 | | article_id | varchar | 250 | 文章id | - |
115 | | article_url | varchar | 250 | 文章链接 | - |
116 | | user_id | varchar | 30 | 用户id | - |
117 | | type | int | 1 | 收藏类型 | 0 收藏 / 1 喜欢 |
118 | | create_time | date | 100 | 编译时间 | - |
119 |
120 | ### 关注表 t_follow
121 |
122 | | 字段 | 类型 | 字段长度 | 说明 | 备注 |
123 | | :------: |:------:|:------: | :------: | :------: |
124 | | id | varchar | 30 | 表主键id | - |
125 | | author_id | varchar | 250 | 作者id | - |
126 | | user_id | varchar | 30 | 用户id | - |
127 | | create_time | date | 100 | 编译时间 | - |
128 |
--------------------------------------------------------------------------------
/app/controller/article.js:
--------------------------------------------------------------------------------
1 | const Controller = require('egg').Controller
2 | const cheerio = require('cheerio');
3 |
4 | class ArticleController extends Controller {
5 | // 获取七牛token
6 | async getQiniuToken() {
7 | const { ctx } = this
8 | let resMsg = {
9 | code: 0,
10 | data: {},
11 | msg: '获取token成功'
12 | }
13 | let uploadToken = await ctx.service.article.getQiniuToken()
14 | console.log(uploadToken)
15 | resMsg.data.token = uploadToken
16 | ctx.success(resMsg)
17 | }
18 | // 获取所有文章列表
19 | async getArticleList() {
20 | const { ctx } = this
21 | const query = {
22 | offset: ctx.helper.parseInt(ctx.query.offset || 0),
23 | limit: ctx.helper.parseInt(ctx.query.limit || 10),
24 | };
25 | let data = await ctx.service.article.getArticleList(query)
26 | ctx.success(data)
27 | }
28 |
29 | // 根据类型id获取文章列表
30 | async getArticleListByTypes() {
31 | const { ctx } = this
32 | let type_id = ctx.query.type_id
33 | let wheres = {
34 | type_id
35 | }
36 | let data = await ctx.service.article.getArticleListByOther(wheres)
37 | ctx.success(data)
38 | }
39 |
40 | // 根据Id获取文章
41 | async getArticleById() {
42 | const { ctx } = this
43 | let id = ctx.query.id
44 | if (!id) {
45 | ctx.error(404, '文章id不能为空')
46 | }
47 | let data = await ctx.service.article.getArticleById(id)
48 | ctx.success(data)
49 | }
50 |
51 | // 根据文章id 获取文章详情
52 | async getArticleDetailById() {
53 | const { ctx } = this
54 | let id = ctx.query.id
55 | if (!id) {
56 | ctx.error(404, '文章id不能为空')
57 | }
58 | let data = await ctx.service.article.getArticleById(id)
59 | let user = await ctx.service.user.find(data.data.user_id)
60 | let users = {
61 | id: user.data.id,
62 | username: user.data.username,
63 | header_url: user.data.header_url
64 | };
65 |
66 | ctx.success({ article: data.data, user: users })
67 | }
68 |
69 | // 根据typeId创建文章
70 | async addArticleByTypeId() {
71 | const { ctx } = this
72 | const { type_id } = ctx.request.body
73 | let query = {
74 | type_id,
75 | user_id: ctx.userId
76 | }
77 | let data = await ctx.service.article.create(query)
78 | ctx.success(data)
79 | }
80 |
81 | // 真实删除文章
82 | async del() {
83 | const ctx = this.ctx;
84 | const { id } = ctx.request.body
85 | const article = await ctx.service.article.del(id);
86 | if (article.code === 0) {
87 | ctx.success(article.data)
88 | } else {
89 | ctx.error(article.code, article.msg)
90 | }
91 | }
92 |
93 | // 修改状态,删除文章
94 | async updateStatus() {
95 | const ctx = this.ctx;
96 | const { id, status, backId } = ctx.request.body
97 | let query = {
98 | id,
99 | backId,
100 | status
101 | }
102 | const article = await ctx.service.article.update(query);
103 | if (article.code === 0) {
104 | ctx.success(article.data)
105 | } else {
106 | ctx.error(article.code, article.msg)
107 | }
108 | }
109 |
110 | // 更新文章
111 | async update() {
112 | const ctx = this.ctx;
113 | const { query, html } = ctx.request.body
114 | let $ = cheerio.load('
' + html + '
');
115 | let text = $('.html').text().replace(/\s+/g, '');
116 | query.text = text;
117 | query.article_num = text.length;
118 | if (text.length > 254) { // 描述文字长度控制在255个字就ok
119 | query.text = text.substring(0, 254)
120 | }
121 | let images = $('.html').find('img');
122 | let imgList = [];
123 | // 保存3张图片就够了
124 | images.each(function(index) {
125 | if (index > 2) {
126 | return false;
127 | }
128 | imgList.push($(this).attr('src'))
129 | })
130 | query.images = JSON.stringify(imgList)
131 | const article = await ctx.service.article.update(query);
132 | if (article.code === 0) {
133 | ctx.success(article.data)
134 | } else {
135 | ctx.error(article.code, article.msg)
136 | }
137 | }
138 |
139 | // 修改发布状态
140 | async backEdit() {
141 | const ctx = this.ctx;
142 | const { query, html } = ctx.request.body
143 | let $ = cheerio.load('' + html + '
');
144 | let text = $('.html').text().replace(/\s+/g, '');
145 | query.text = text;
146 | query.article_num = text.length;
147 | if (text.length > 254) { // 描述文字长度控制在255个字就ok
148 | query.text = text.substring(0, 254)
149 | }
150 | let images = $('.html').find('img');
151 | let imgList = [];
152 | // 保存3张图片就够了
153 | images.each(function(index) {
154 | if (index > 2) {
155 | return false;
156 | }
157 | imgList.push($(this).attr('src'))
158 | })
159 | query.images = JSON.stringify(imgList)
160 | query.text = text;
161 | query.article_num = text.length
162 | let article = await ctx.service.article.createBack(query);
163 |
164 | if (article.code === 0) {
165 | ctx.success(article.data)
166 | } else {
167 | ctx.error(article.code, article.msg)
168 | }
169 | }
170 |
171 | // 通过类型Id 获取文章列表和备份表数据
172 | async getArticleBackByTypeId() {
173 | const { ctx } = this
174 | let type_id = ctx.query.type_id
175 | let data = await ctx.service.article.getArticleBackByTypeId(type_id)
176 | ctx.success(data)
177 | }
178 | // 根据backId获取文章
179 | async getArticleBackById() {
180 | const { ctx } = this
181 | let id = ctx.query.id
182 | if (!id) {
183 | ctx.error(404, '文章id不能为空')
184 | }
185 | let data = await ctx.service.article.getArticleBackById(id)
186 | ctx.success(data)
187 | }
188 | // 获取评论
189 | async getCommentListByArticleId() {
190 | const { ctx } = this
191 | const query = {
192 | article_id: ctx.query.article_id,
193 | user_id: ctx.query.user_id,
194 | offset: ctx.helper.parseInt(ctx.query.offset || 0),
195 | limit: ctx.helper.parseInt(ctx.query.limit || 10),
196 | };
197 | let data = await ctx.service.comment.getCommentList(query);
198 | ctx.success(data);
199 | }
200 | // 添加评论
201 | async addComment() {
202 | const { ctx } = this
203 | const { query } = ctx.request.body
204 | let user_id = ctx.userId
205 | if (user_id) {
206 | ctx.error(401, '请先登录')
207 | }
208 | query.user_id = user_id
209 | let data = await ctx.service.comment.create(query)
210 | if (data.code === 0) {
211 | ctx.success(data)
212 | } else {
213 | ctx.error(data)
214 | }
215 | }
216 | // 添加评论回复
217 | async addCommentReply() {
218 | const { ctx } = this
219 | const { query } = ctx.request.body
220 | let user_id = ctx.userId
221 | if (user_id) {
222 | ctx.error(401, '请先登录')
223 | }
224 | query.from_user_id = user_id
225 | let data = await ctx.service.comment.createReply(query)
226 | ctx.success(data)
227 | }
228 | // 评论点赞
229 | async addCommentLoveNum() {
230 | const { ctx } = this
231 | const { id, LoveId } = ctx.request.body
232 | let data = await ctx.service.comment.LoveNum(id, LoveId)
233 | ctx.success(data)
234 | }
235 | }
236 |
237 | module.exports = ArticleController
238 |
--------------------------------------------------------------------------------
/app/service/article.js:
--------------------------------------------------------------------------------
1 | const Service = require('egg').Service;
2 | const qiniu = require('qiniu')
3 | class ArticleService extends Service {
4 | // 根据用户id获取文章列表
5 | async getArticleList({ offset = 0, limit = 10, user_id }) {
6 | const options = {
7 | offset,
8 | limit,
9 | attributes: [ 'id', 'title', 'text', 'images', 'user_id', 'type_id', 'article_num', 'ready_num', 'like_num', 'comment_num', 'status', 'created_at', 'updated_at' ],
10 | order: [[ 'created_at', 'desc' ], [ 'id', 'desc' ]],
11 | include: {
12 | attributes: [ 'username', 'header_url' ],
13 | model: this.ctx.model.Users
14 | }
15 | };
16 | options.where = {
17 | status: 0
18 | }
19 | if (user_id) {
20 | options.where = {
21 | user_id,
22 | };
23 | }
24 | let data = await this.ctx.model.Article.findAndCountAll(options)
25 | data.rows = data.rows.map(info => {
26 | if (info.images) {
27 | info.images = JSON.parse(info.images)
28 | } else {
29 | info.images = [];
30 | }
31 | return info
32 | })
33 | return data;
34 | }
35 | // 根据其他条件添加
36 | async getArticleListByOther(wheres, offset, limit) {
37 | const options = {
38 | offset,
39 | limit,
40 | // attributes: [ 'id', 'title', 'content', 'user_id', 'created_at', 'updated_at' ],
41 | order: [[ 'created_at', 'desc' ], [ 'id', 'desc' ]],
42 | };
43 | if (offset) {
44 | options.offset = offset
45 | }
46 | if (limit) {
47 | options.limit = limit
48 | }
49 | if (wheres) {
50 | options.where = wheres
51 | options.where.status = [ 0, 1, 2 ]
52 | } else {
53 | options.where = {
54 | status: [ 0, 1, 2 ]
55 | }
56 | }
57 | return this.ctx.model.Article.findAndCountAll(options);
58 | }
59 | // 根据Id
60 | async getArticleById(id) {
61 | let data = await this.ctx.model.Article.findById(id);
62 | if (!data) {
63 | return {
64 | code: 404,
65 | msg: '不存在文集类型'
66 | }
67 | }
68 | data.ready_num = data.ready_num + 1;
69 | this.addReadyNum(data.id)
70 | return {
71 | code: 0,
72 | data: data
73 | }
74 | }
75 | // 阅读数加 1
76 | async addReadyNum(id) {
77 | let data = this.app.model.query('update t_articles set ready_num = ready_num+1 where id=:id', { type: 'UPDATE', replacements: { id: id } }).then(results => results);
78 | return data
79 | }
80 | // 评论数加 1
81 | async addCommentNum(id) {
82 | let data = this.app.model.query('update t_articles set comment_num = comment_num+1 where id=:id', { type: 'UPDATE', replacements: { id: id } }).then(results => results);
83 | return data
84 | }
85 | // 新增
86 | async create(query) {
87 | const { ctx } = this;
88 | // 使用事务管理
89 | try {
90 | let option = {
91 | title: '标题栏',
92 | content: ' ',
93 | user_id: query.user_id,
94 | type_id: query.type_id,
95 | article_num: 0,
96 | ready_num: 0,
97 | like_num: 0,
98 | comment_num: 0,
99 | status: 1,
100 | created_at: new Date(),
101 | updated_at: new Date(),
102 | }
103 | let data = await ctx.model.Article.create(option);
104 | let optionBack = {
105 | article_id: data.id,
106 | type_id: data.type_id,
107 | title: data.title,
108 | text: '',
109 | content: '',
110 | article_num: 0,
111 | status: 0,
112 | created_at: new Date(),
113 | updated_at: new Date(),
114 | }
115 | let articleBack = await ctx.model.ArticleBack.create(optionBack);
116 | return {
117 | code: 0,
118 | data: {
119 | id: data.id,
120 | status: data.status,
121 | backId: articleBack.id,
122 | title: data.title,
123 | text: '',
124 | article_num: 0,
125 | }
126 | }
127 | } catch (e) {
128 |
129 | return {
130 | code: 1,
131 | msg: '插入失败'
132 | }
133 | }
134 | }
135 | async del(id) {
136 | const article = await this.ctx.model.Article.findById(id);
137 | if (!article) {
138 | return {
139 | code: 404,
140 | msg: '不存在文集类型'
141 | }
142 | }
143 | let data = await article.destroy();
144 | return {
145 | code: 0,
146 | data: data.dataValues
147 | }
148 | }
149 | async update(query) {
150 | const article = await this.ctx.model.Article.findById(query.id);
151 | if (!article) {
152 | return {
153 | code: 404,
154 | msg: '文章已被删除'
155 | }
156 | }
157 | let option = {}
158 | if (query.id) {
159 | option.id = query.id
160 | }
161 | option.status = query.status
162 | if (query.status === 0) { // 0 发布 重备份表获取title,content.....
163 | let backup = await this.ctx.model.ArticleBack.findById(query.backId)
164 | if (backup) {
165 | option.title = backup.title
166 | option.text = backup.text
167 | option.images = backup.images
168 | option.content = backup.content
169 | option.article_num = backup.article_num
170 | option.updated_at = new Date()
171 | }
172 | }
173 | let data = await article.update(option);
174 | return {
175 | code: 0,
176 | data: data
177 | }
178 | }
179 | // 生成七牛token
180 | async getQiniuToken() {
181 | const { app } = this
182 | // 这里需要七牛的Access Key和Secret Key
183 | let mac = new qiniu.auth.digest.Mac(app.config.qiniu.ak, app.config.qiniu.sk);
184 | let options = {
185 | scope: 'jianshu',
186 | };
187 | let putPolicy = new qiniu.rs.PutPolicy(options);
188 | let uploadToken = putPolicy.uploadToken(mac);
189 | return uploadToken
190 | }
191 |
192 | // 备份表数据更新
193 | async createBack(query) {
194 | const { ctx } = this;
195 | let option = {
196 | article_id: query.article_id,
197 | type_id: query.type_id,
198 | title: query.title,
199 | images: query.images,
200 | content: query.content,
201 | text: query.text,
202 | article_num: query.article_num,
203 | status: 0,
204 | created_at: new Date(),
205 | updated_at: new Date(),
206 | }
207 | let data = await ctx.model.ArticleBack.create(option);
208 | if (!data) {
209 | return {
210 | code: 1,
211 | msg: '插入失败'
212 | }
213 | }
214 | return {
215 | code: 0,
216 | data: data.dataValues
217 | }
218 |
219 | }
220 | // 备份表更新
221 | async updateBack(query) {
222 | const articleBack = await this.ctx.model.ArticleBack.findById(query.id);
223 | if (!articleBack) {
224 | return {
225 | code: 404,
226 | msg: '文章已被删除'
227 | }
228 | }
229 | let data = await articleBack.update(query);
230 | return {
231 | code: 0,
232 | data: data
233 | }
234 | }
235 | // 根据类型ID 获取文章列表
236 | async getArticleBackByTypeId(typeId) {
237 | const options = {
238 | attributes: [ 'id', 'status' ],
239 | where: { type_id: typeId, status: [ 0, 1, 2 ] },
240 | order: [[ 'updated_at', 'desc' ], [ 'id', 'desc' ]]
241 | };
242 | let articles = await this.ctx.model.Article.findAndCountAll(options);
243 | let rows = []
244 | for (let info of articles.rows) {
245 | let op = {
246 | where: { article_id: info.id },
247 | order: [[ 'updated_at', 'desc' ]],
248 | limit: 1
249 | }
250 | let articleBack = await this.ctx.model.ArticleBack.findAndCountAll(op)
251 | rows.push({
252 | id: info.id,
253 | status: info.status,
254 | backId: articleBack.rows[0].id,
255 | title: articleBack.rows[0].title,
256 | text: articleBack.rows[0].text,
257 | article_num: articleBack.rows[0].title,
258 | })
259 | }
260 | articles.rows = rows;
261 | return articles
262 | }
263 | // 根据backId 获取备份表信息
264 | async getArticleBackById(id) {
265 | let data = await this.ctx.model.ArticleBack.findById(id);
266 | if (!data) {
267 | return {
268 | code: 404,
269 | msg: '不存在文集类型'
270 | }
271 | }
272 | return {
273 | code: 0,
274 | data: data
275 | }
276 | }
277 | }
278 |
279 | module.exports = ArticleService
280 |
--------------------------------------------------------------------------------