├── .babelrc
├── src
├── routes
│ ├── index.js
│ ├── modes.js
│ ├── admin.js
│ ├── util.js
│ ├── notes.js
│ └── users.js
├── models
│ ├── badge.js
│ ├── award.js
│ ├── token.js
│ ├── code.js
│ ├── report.js
│ ├── version.js
│ ├── message.js
│ ├── feedback.js
│ ├── comment.js
│ ├── activity.js
│ ├── note.js
│ ├── util.js
│ ├── user.js
│ └── index.js
├── test
│ └── test.js
├── config
│ ├── sequelize.js
│ └── index.js
├── utils.js
└── app.js
├── .eslintrc
├── .gitignore
├── gulpfile.babel.js
├── package.json
├── docs
├── Model.md
└── API.md
├── README.md
└── LICENSE
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "stage-2"
5 | ],
6 | "plugins": [
7 | [
8 | "transform-runtime",
9 | {
10 | "regenerator": true
11 | }
12 | ]
13 | ]
14 | }
--------------------------------------------------------------------------------
/src/routes/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 |
3 | const router = express.Router()
4 |
5 | router.get('/', (req, res) => {
6 | return res.json({title: 'Express'})
7 | })
8 |
9 | module.exports = router
10 |
--------------------------------------------------------------------------------
/src/models/badge.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 |
3 | const { STRING } = DataTypes
4 |
5 | return sequelize.define('badge', {
6 | 'name': STRING(45),
7 | 'image': STRING(45),
8 | 'bio': STRING(125),
9 | })
10 | }
11 |
--------------------------------------------------------------------------------
/src/models/award.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 |
3 | const { BOOLEAN, INTEGER, STRING } = DataTypes
4 |
5 | return sequelize.define('award', {
6 | 'code': STRING(45),
7 | 'badge_id': INTEGER,
8 | 'used': BOOLEAN,
9 | })
10 | }
11 |
--------------------------------------------------------------------------------
/src/models/token.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 |
3 | const { STRING, BOOLEAN, DOUBLE } = DataTypes
4 |
5 | return sequelize.define('token', {
6 | 'code': STRING(100),
7 | 'deadline': DOUBLE,
8 | 'alive': BOOLEAN
9 | })
10 | }
11 |
--------------------------------------------------------------------------------
/src/test/test.js:
--------------------------------------------------------------------------------
1 | const request = require('supertest')('http://localhost:3002')
2 |
3 | describe('#test server', () => {
4 |
5 | it('#test GET /', async (done) => {
6 | await request.get('/')
7 | .expect('Content-Type', /application\/json/)
8 | .then(done())
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/src/models/code.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 |
3 | const { STRING, BOOLEAN, DOUBLE } = DataTypes
4 |
5 | return sequelize.define('code', {
6 | 'account': STRING(20),
7 | 'timestamp': DOUBLE,
8 | 'used': BOOLEAN,
9 | 'code': STRING(20),
10 | })
11 | }
12 |
--------------------------------------------------------------------------------
/src/models/report.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 |
3 | const {
4 | INTEGER
5 | } = DataTypes
6 |
7 | return sequelize.define('report', {
8 | 'note_id': INTEGER,
9 | 'user_id': INTEGER, // 举报者 id
10 | 'pass': INTEGER, // 处理结果: 0 未处理,1 通过,2 不通过
11 | })
12 | }
13 |
--------------------------------------------------------------------------------
/src/models/version.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 |
3 | const {
4 | STRING,
5 | INTEGER,
6 | } = DataTypes
7 |
8 | return sequelize.define('version', {
9 | 'version': STRING(100),
10 | 'platform': INTEGER, // 平台码:1-ios, 2-Android, 3-wxapp
11 | 'status': INTEGER, // 状态码:0-当前最新版,1-稳定版(兼容), 2-beta, 3-废弃(不兼容)
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/src/models/message.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 |
3 | const { DOUBLE, STRING, INTEGER } = DataTypes
4 |
5 | return sequelize.define('message', {
6 | 'title': STRING(125),
7 | 'type': INTEGER,
8 | 'content': STRING(500),
9 | 'image': STRING(125),
10 | 'url': STRING(125),
11 | 'date': DOUBLE,
12 | 'user_id': INTEGER
13 | })
14 | }
15 |
--------------------------------------------------------------------------------
/src/models/feedback.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 |
3 | const { TEXT, INTEGER, STRING } = DataTypes
4 |
5 | return sequelize.define('feedback', {
6 | 'user_id': INTEGER,
7 | 'title': STRING(135),
8 | 'content': TEXT,
9 | 'type': INTEGER,
10 | 'version': STRING(25),
11 | 'brand': STRING(35),
12 | 'system_version': STRING(35),
13 | })
14 | }
15 |
--------------------------------------------------------------------------------
/src/models/comment.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 |
3 | const {
4 | TEXT,
5 | INTEGER,
6 | DOUBLE
7 | } = DataTypes
8 |
9 | return sequelize.define('comment', {
10 | 'user_id': INTEGER,
11 | 'reply_id': INTEGER,
12 | 'note_id': INTEGER,
13 | 'content': TEXT,
14 | 'date': DOUBLE,
15 | 'delete': INTEGER, // 是否删除,0 未删除,1 已删除
16 | })
17 | }
18 |
--------------------------------------------------------------------------------
/src/models/activity.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 |
3 | const { INTEGER, DOUBLE, BOOLEAN } = DataTypes
4 |
5 | return sequelize.define('act_1', {
6 | 'user_id': INTEGER, // 报名用户id
7 | 'user_other_id': INTEGER, // 另一半用户id
8 | 'state': INTEGER, // 用户报名时的状态,用于匹配
9 | 'success': BOOLEAN, // 是否报名成功
10 | 'process': DOUBLE, // 活动完成度
11 | 'gold': DOUBLE, // 活动目标
12 | 'finished': BOOLEAN, // 活动是否完成
13 | 'beginline': DOUBLE, // 活动开始时间的时间戳
14 | 'deadline': DOUBLE, // 活动截止时间的时间戳
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/src/models/note.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 |
3 | const { TEXT, DOUBLE, INTEGER, STRING } = DataTypes
4 |
5 | return sequelize.define('note', {
6 | 'user_id': INTEGER,
7 | 'title': TEXT,
8 | 'content': TEXT,
9 | 'images': TEXT,
10 | 'latitude': DOUBLE,
11 | 'longitude': DOUBLE,
12 | 'location': STRING(245),
13 | 'is_liked': INTEGER,
14 | 'mode': DOUBLE,
15 | 'date': DOUBLE,
16 | 'status': INTEGER,
17 | 'uuid': STRING(100),
18 | 'hole_alive': DOUBLE, // 树洞下线时间:-1 为不发布到树洞。
19 | 'v': INTEGER
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/src/config/sequelize.js:
--------------------------------------------------------------------------------
1 | import Sequelize from 'sequelize'
2 |
3 | import {SQL_USER, SQL_PASSWORD} from './index'
4 |
5 | export const dbConnect = () => {
6 | return new Sequelize('twolife2', SQL_USER, SQL_PASSWORD,
7 | {
8 | 'dialect': 'mysql',
9 | 'dialectOptions': {
10 | charset: 'utf8mb4',
11 | collate: 'utf8mb4_unicode_ci',
12 | supportBigNumbers: true,
13 | bigNumberStrings: true
14 | },
15 | 'host': 'localhost',
16 | 'port': 3306,
17 | 'define': {
18 | 'underscored': true // 字段以下划线(_)来分割(默认是驼峰命名风格)
19 | }
20 | }
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/models/util.js:
--------------------------------------------------------------------------------
1 | export const findAll = async (model, condition, include) => {
2 | return await model.findAll({
3 | where: condition,
4 | include: include
5 | })
6 | }
7 |
8 | export const findOne = async (model, condition, option) => {
9 | return await model.findOne({
10 | where: condition
11 | }, option)
12 | }
13 |
14 | export const create = async (model, data) => {
15 | return await model.create(data)
16 | }
17 |
18 | export const remove = async (model, condition) => {
19 | return await model.destroy({
20 | where: condition
21 | })
22 | }
23 |
24 | export const update = async (model, data, condition) => {
25 | return await model.update(data, {
26 | where: condition
27 | })
28 | }
29 |
--------------------------------------------------------------------------------
/src/routes/modes.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 |
3 | import { Note } from '../models'
4 |
5 | import {
6 | MESSAGE,
7 | validate
8 | } from '../config'
9 |
10 | const router = express.Router()
11 |
12 | router.get('/show', (req, res) => {
13 |
14 | const { uid, token, timestamp } = req.query
15 | validate(res, true, uid, timestamp, token)
16 |
17 | const response = async () => {
18 | const notes = await Note.findAll({ where: { user_id: uid } })
19 |
20 | const data = await notes.map(n => {
21 | const d = {}
22 | d[n.dataValues.date] = n.dataValues.mode
23 | return d
24 | })
25 | return res.json({ ...MESSAGE.OK, data })
26 | }
27 | response()
28 | })
29 |
30 | module.exports = router
31 |
--------------------------------------------------------------------------------
/src/models/user.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 |
3 | const { STRING, INTEGER, DOUBLE, TEXT } = DataTypes
4 |
5 | return sequelize.define('user', {
6 | 'code': STRING(45),
7 | 'account': STRING(45),
8 | 'password': STRING(45),
9 | 'name': STRING(45),
10 | 'sex': INTEGER,
11 | 'face': STRING(250),
12 | 'status': INTEGER,
13 | 'total_notes': INTEGER,
14 | 'mode': DOUBLE,
15 | 'last_times': INTEGER,
16 | 'total_times': INTEGER,
17 | 'badges': STRING(500),
18 | 'badge_id': INTEGER,
19 | 'rate': DOUBLE,
20 | 'connect_at': DOUBLE,
21 | 'openid': STRING(45),
22 | 'user_other_id': INTEGER,
23 | 'ban_id': TEXT,
24 | 'unread': INTEGER,
25 | 'latitude': DOUBLE,
26 | 'longitude': DOUBLE,
27 | 'emotions_basis': STRING(200),
28 | 'emotions': STRING(200),
29 | 'emotions_type': STRING(45),
30 | 'emotions_report': TEXT,
31 | 'vip': INTEGER,
32 | 'vip_expires': DOUBLE,
33 | 'date': DOUBLE,
34 | 'area': STRING(10)
35 | })
36 | }
37 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true
4 | },
5 | "parser": "babel-eslint",
6 | "globals": {
7 | "angular": true
8 | },
9 | "rules": {
10 | "eqeqeq": 2,
11 | "camelcase": 0,
12 | "curly": 0,
13 | "brace-style": [
14 | 2,
15 | "1tbs"
16 | ],
17 | "quotes": [
18 | 2,
19 | "single"
20 | ],
21 | "semi": [
22 | 2,
23 | "never"
24 | ],
25 | "array-bracket-spacing": [
26 | 2,
27 | "never"
28 | ],
29 | "indent": [
30 | "error",
31 | 2,
32 | {
33 | "SwitchCase": 1
34 | }
35 | ],
36 | "space-infix-ops": 2,
37 | "comma-spacing": 2,
38 | "no-unused-vars": 2,
39 | "no-var": 2,
40 | "no-mixed-spaces-and-tabs": 2,
41 | "no-multi-spaces": 2,
42 | "no-console": 0,
43 | "keyword-spacing": 2,
44 | "space-before-function-paren": 0,
45 | "block-spacing": 2,
46 | "comma-dangle": 0,
47 | "comma-style": 2,
48 | "dot-location": 0,
49 | "eol-last": 2,
50 | "key-spacing": 2,
51 | "no-const-assign": 2,
52 | "space-in-parens": 2,
53 | "space-before-blocks": 2,
54 | "semi-spacing": 2,
55 | "rest-spread-spacing": 2,
56 | "no-redeclare": 2,
57 | "spaced-comment": 2
58 | }
59 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | .DS_Store
61 |
62 | dist/
63 | .idea/
64 | logs/
65 | mochawesome-report/
66 |
67 | package-lock.json
68 | src/config/index.js
69 |
--------------------------------------------------------------------------------
/gulpfile.babel.js:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp'
2 | import watch from 'gulp-watch'
3 | import babel from 'gulp-babel'
4 | import eslint from 'gulp-eslint'
5 | import nodemon from 'gulp-nodemon'
6 |
7 | gulp.task('lint', () => {
8 | return gulp.src(['src/**/*.js', '!node_modules/**'])
9 | .pipe(eslint())
10 | .pipe(eslint.format())
11 | .pipe(eslint.failAfterError())
12 | })
13 |
14 | gulp.task('transform', () => {
15 | return gulp.src('src/**/*.js')
16 | .pipe(babel({
17 | presets: ['es2015', 'stage-2']
18 | }))
19 | .pipe(gulp.dest('dist'))
20 | })
21 |
22 | gulp.task('watch', () => {
23 | return gulp.src('src/**/*.js')
24 | .pipe(watch('src/**/*.js', {
25 | verbose: true
26 | }))
27 | .pipe(eslint({configFle: './.eslintrc'}))
28 | .pipe(eslint.format())
29 | .pipe(babel())
30 | .pipe(gulp.dest('dist'))
31 | })
32 |
33 | gulp.task('dev', () => {
34 | gulp.start('transform')
35 | gulp.start('watch')
36 | nodemon({
37 | script: 'dist/app.js',
38 | ext: 'js',
39 | env: {'NODE_ENV': 'development'}
40 | })
41 | })
42 |
43 | gulp.task('default', ['transform', 'lint'], () => {
44 | nodemon({
45 | script: 'dist/app.js',
46 | ext: 'js',
47 | watch: 'src',
48 | tasks: ['transform']
49 | })
50 | })
51 |
--------------------------------------------------------------------------------
/src/models/index.js:
--------------------------------------------------------------------------------
1 | const db = require('../config/sequelize').dbConnect()
2 |
3 | const User = db.import('./user')
4 | const Note = db.import('./note')
5 | const Comment = db.import('./comment')
6 | const Badge = db.import('./badge')
7 | const Award = db.import('./award')
8 | const Feedback = db.import('./feedback')
9 | const Code = db.import('./code')
10 | const Message = db.import('./message')
11 | const Token = db.import('./token')
12 | const Activity = db.import('./activity')
13 | const Version = db.import('./version')
14 | const Report = db.import('./report')
15 |
16 | User.hasMany(Note, { foreignKey: 'user_id' })
17 | User.hasMany(Feedback, { foreignKey: 'user_id' })
18 | User.hasMany(Comment, { foreignKey: 'user_id'})
19 | User.hasMany(Comment, { foreignKey: 'reply_id'})
20 | Note.hasMany(Comment, { foreignKey: 'note_id' })
21 |
22 | Award.hasOne(Badge, { foreignKey: 'badge_id' })
23 | User.hasOne(Badge, { foreignKey: 'badge_id' })
24 |
25 | Note.belongsTo(User)
26 | Comment.belongsTo(User, { foreignKey: 'user_id', as: 'user' })
27 | Comment.belongsTo(User, { foreignKey: 'reply_id', as: 'reply' })
28 | Comment.belongsTo(Note)
29 | Feedback.belongsTo(User)
30 | Badge.belongsTo(Award)
31 | Badge.belongsTo(User)
32 | Report.belongsTo(User, { foreignKey: 'user_id' })
33 | Report.belongsTo(Note, { foreignKey: 'note_id' })
34 |
35 | db.sync()
36 |
37 | module.exports = {
38 | User,
39 | Note,
40 | Comment,
41 | Badge,
42 | Award,
43 | Feedback,
44 | Code,
45 | Message,
46 | Token,
47 | Activity,
48 | Version,
49 | Report
50 | }
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "twolife",
3 | "version": "2.0.0",
4 | "private": true,
5 | "author": "Airing",
6 | "scripts": {
7 | "start": "gulp",
8 | "dev": "gulp dev",
9 | "build": "gulp transform",
10 | "server": "nodemon dist/app.js --exec babel-node",
11 | "test": "mocha --reporter mochawesome src/test/test.js"
12 | },
13 | "dependencies": {
14 | "Promise": "^1.0.5",
15 | "body-parser": "~1.16.0",
16 | "crypto-js": "^3.1.9-1",
17 | "express": "~4.14.1",
18 | "express-rate-limit": "^3.3.2",
19 | "jpush-async": "^4.0.0-rc.1",
20 | "log4js": "^2.7.0",
21 | "md5": "^2.2.1",
22 | "mysql": "^2.13.0",
23 | "natives": "^1.1.6",
24 | "node-fetch": "^2.1.2",
25 | "qcloudapi-sdk": "^0.1.6",
26 | "qiniu": "^6.1.13",
27 | "redis": "^2.8.0",
28 | "request-promise": "^4.2.2",
29 | "sequelize": "^3.31.1",
30 | "sha1": "^1.1.1",
31 | "tencentcloud-sdk-nodejs": "^3.0.94"
32 | },
33 | "devDependencies": {
34 | "babel-cli": "^6.26.0",
35 | "babel-core": "^6.26.0",
36 | "babel-eslint": "^8.0.2",
37 | "babel-loader": "^7.1.2",
38 | "babel-plugin-transform-runtime": "^6.23.0",
39 | "babel-preset-es2015": "^6.24.1",
40 | "babel-preset-stage-2": "^6.24.1",
41 | "babel-register": "^6.26.0",
42 | "eslint": "^4.12.1",
43 | "gulp": "^3.9.0",
44 | "gulp-babel": "^7.0.0",
45 | "gulp-cli": "^1.4.0",
46 | "gulp-eslint": "^4.0.0",
47 | "gulp-nodemon": "^2.2.1",
48 | "gulp-watch": "^4.3.11",
49 | "mocha": "^5.0.5",
50 | "mochawesome": "^3.0.2",
51 | "nodemon": "^1.12.1",
52 | "supertest": "^3.0.0"
53 | },
54 | "eslintConfig": {
55 | "env": {
56 | "browser": true,
57 | "node": true
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/docs/Model.md:
--------------------------------------------------------------------------------
1 | # “双生” 服务端模型文档
2 |
3 | 1. 用户模块
4 | 2. 日记模块
5 | 3. 通知模块
6 |
7 | ## 用户模块
8 | ### 1. User
9 | * id
10 | * code:用户id,匹配用
11 | * account
12 | * password
13 | * name
14 | * sex
15 | * face
16 | * status:用户的状态(详见数据字典)
17 | * total\_notes:日记总数
18 | * mode:平均情绪值
19 | * last\_times:本月剩余匹配次数
20 | * total\_times:已匹配次数
21 | * badges:已拥有徽章id,英文逗号隔开
22 | * badge\_id:用户选择展示的徽章id
23 | * rate:用户评分
24 | * connect\_at:最近一次匹配时间
25 | * openid:微信openid
26 | * other\_user\_id:匹配用户id
27 | * ban\_id:用户黑名单,英文逗号隔开
28 | * unread:用户未读消息数
29 |
30 | #### 数据字典
31 | sex:
32 | * 0:男
33 | * 1:女
34 |
35 | status:
36 | * 0:未匹配,刚解除匹配的临界状态
37 | * 101:未匹配,期待异性,性格相同,主体男
38 | * 102:未匹配,期待异性,性格互补,主体男
39 | * 103:未匹配,期待异性,性格随意,主体男
40 | * 111:未匹配,期待异性,性格相同,主体女
41 | * 112:未匹配,期待异性,性格互补,主体女
42 | * 113:未匹配,期待异性,性格随意,主体女
43 | * 201:未匹配,期待同性,性格相同,主体男
44 | * 202:未匹配,期待同性,性格互补,主体男
45 | * 203:未匹配,期待同性,性格随意,主体男
46 | * 211:未匹配,期待同性,性格相同,主体女
47 | * 212:未匹配,期待同性,性格互补,主体女
48 | * 213:未匹配,期待同性,性格随意,主体女
49 | * 501:无匹配次数
50 | * 502:无匹配权限(无日记)
51 | * 503:无匹配权限(永久封禁)
52 | * 504:无匹配权限(有限封禁)
53 | * 999:关闭匹配
54 | * 1000:已匹配
55 |
56 | ### 2. Badge
57 | * id
58 | * name
59 | * image
60 | * bio
61 |
62 | ### 3. Award
63 | * id
64 | * code
65 | * badge\_id
66 | * used
67 |
68 | ## 日记模块
69 | ### 1. Note
70 | * id
71 | * user\_id
72 | * title
73 | * content
74 | * images:日记的配图url,英文逗号隔开
75 | * latitude
76 | * longitude
77 | * location
78 | * is\_liked
79 | * mode
80 | * date: 创建日记时间戳
81 | * status:写日记时用户的状态
82 |
83 | #### 数据字典
84 | is\_liked:
85 | * 0:未被喜欢
86 | * 1:已被喜欢
87 |
88 | ## 通知模块
89 | ### 1. Message
90 | * id
91 | * title
92 | * type
93 | * content
94 | * image
95 | * url
96 | * date:创建通知的时间戳
97 | * user\_id
98 |
99 | #### 数据字典
100 | type:
101 | * 0:系统消息(被ban、无次数等)
102 | * 101:通知(无url,有content)
103 | * 102:活动、宣传等(有url,有content)
104 | * 201:被匹配(无url,无content)
105 | * 202:被解除匹配(无url,无content)
106 | * 203:被喜欢(无url,无content)
107 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # “双生” 服务端文档
2 |
3 | 包括服务端接口文档与模型文档(见docs),仅供学习使用,严禁商业用途。
4 |
5 | 同时热烈欢迎pr~
6 |
7 | 1. 用户模块
8 | 2. 日记模块
9 | 3. 通知模块
10 |
11 | ## 用户模块
12 | ### 1. User
13 | * id
14 | * code:用户id,匹配用
15 | * account
16 | * password
17 | * name
18 | * sex
19 | * face
20 | * status:用户的状态(详见数据字典)
21 | * total\_notes:日记总数
22 | * mode:平均情绪值
23 | * last\_times:本月剩余匹配次数
24 | * total\_times:已匹配次数
25 | * badges:已拥有徽章id,英文逗号隔开
26 | * badge\_id:用户选择展示的徽章id
27 | * rate:用户评分
28 | * connect\_at:最近一次匹配时间
29 | * openid:微信openid
30 | * other\_user\_id:匹配用户id
31 | * ban\_id:用户黑名单,英文逗号隔开
32 | * unread:用户未读消息数
33 |
34 | #### 数据字典
35 | sex:
36 | * 0:男
37 | * 1:女
38 |
39 | status:
40 | * 0:未匹配,刚解除匹配的临界状态
41 | * 101:未匹配,期待异性,性格相同,主体男
42 | * 102:未匹配,期待异性,性格互补,主体男
43 | * 103:未匹配,期待异性,性格随意,主体男
44 | * 111:未匹配,期待异性,性格相同,主体女
45 | * 112:未匹配,期待异性,性格互补,主体女
46 | * 113:未匹配,期待异性,性格随意,主体女
47 | * 201:未匹配,期待同性,性格相同,主体男
48 | * 202:未匹配,期待同性,性格互补,主体男
49 | * 203:未匹配,期待同性,性格随意,主体男
50 | * 211:未匹配,期待同性,性格相同,主体女
51 | * 212:未匹配,期待同性,性格互补,主体女
52 | * 213:未匹配,期待同性,性格随意,主体女
53 | * 501:无匹配次数
54 | * 502:无匹配权限(无日记)
55 | * 503:无匹配权限(永久封禁)
56 | * 504:无匹配权限(有限封禁)
57 | * 999:关闭匹配
58 | * 1000:已匹配
59 |
60 | ### 2. Badge
61 | * id
62 | * name
63 | * image
64 | * bio
65 |
66 | ### 3. Award
67 | * id
68 | * code
69 | * badge\_id
70 | * used
71 |
72 | ## 日记模块
73 | ### 1. Note
74 | * id
75 | * user\_id
76 | * title
77 | * content
78 | * images:日记的配图url,英文逗号隔开
79 | * latitude
80 | * longitude
81 | * location
82 | * is\_liked
83 | * mode
84 | * date: 创建日记时间戳
85 | * status:写日记时用户的状态
86 |
87 | #### 数据字典
88 | is\_liked:
89 | * 0:未被喜欢
90 | * 1:已被喜欢
91 |
92 | ## 通知模块
93 | ### 1. Message
94 | * id
95 | * title
96 | * type
97 | * content
98 | * image
99 | * url
100 | * date:创建通知的时间戳
101 | * user\_id
102 |
103 | #### 数据字典
104 | type:
105 | * 0:系统消息(被ban、无次数等)
106 | * 101:通知(无url,有content)
107 | * 102:活动、宣传等(有url,有content)
108 | * 201:被匹配(无url,无content)
109 | * 202:被解除匹配(无url,无content)
110 | * 203:被喜欢(无url,无content)
111 |
112 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | import { Token } from './models'
2 | import rp from 'request-promise'
3 |
4 | import {
5 | WXP_APPID,
6 | WXP_SECRET
7 | } from './config/index'
8 |
9 |
10 | async function getAccessToken() {
11 | const access_token = await Token.findOne({
12 | where: {
13 | deadline: {
14 | 'gt': Date.now(),
15 | 'lt': Date.now() + 7200000
16 | }
17 | },
18 | raw: true
19 | })
20 |
21 | if (!access_token) {
22 | const options = {
23 | uri: 'https://api.weixin.qq.com/cgi-bin/token',
24 | qs: {
25 | grant_type: 'client_credential',
26 | appid: WXP_APPID,
27 | secret: WXP_SECRET
28 | },
29 | json: true
30 | }
31 | const data = await rp(options)
32 |
33 | await Token.create({
34 | code: data.access_token,
35 | deadline: Date.now() + 7000000, // 官方7200秒,这里预留时间防止前端重复请求
36 | alive: true
37 | })
38 |
39 | return data.access_token
40 |
41 | } else {
42 | return access_token.code
43 | }
44 | }
45 |
46 | async function wechatContentCheck(content) {
47 | if (!content) {
48 | return {
49 | errcode: 0,
50 | errMsg: 'no content'
51 | }
52 | }
53 |
54 | const access_token = await getAccessToken()
55 | const option = {
56 | method: 'POST',
57 | uri: 'https://api.weixin.qq.com/wxa/msg_sec_check?access_token=' + access_token,
58 | body: {
59 | content
60 | },
61 | json: true
62 | }
63 |
64 | return await rp(option)
65 | }
66 |
67 | async function wechatImgCheck(imgUrl) {
68 | if (!imgUrl) {
69 | return {
70 | errcode: 0,
71 | errMsg: 'no image'
72 | }
73 | }
74 |
75 | const access_token = await getAccessToken()
76 | const option = {
77 | method: 'POST',
78 | uri: 'https://api.weixin.qq.com/wxa/img_sec_check?access_token=' + access_token,
79 | formData: {
80 | media: {
81 | value: await rp(imgUrl),
82 | options: {
83 | filename: 'test.jpg',
84 | contentType: 'image/jpg'
85 | }
86 | }
87 | },
88 | json: true
89 | }
90 |
91 | return await rp(option)
92 | }
93 |
94 | exports.getAccessToken = getAccessToken
95 | exports.wechatContentCheck = wechatContentCheck
96 | exports.wechatImgCheck = wechatImgCheck
97 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | import http from 'http'
2 |
3 | import express from 'express'
4 | import bodyParser from 'body-parser'
5 | import index from './routes/index'
6 | import users from './routes/users'
7 | import notes from './routes/notes'
8 | import modes from './routes/modes'
9 | import util from './routes/util'
10 | import admin from './routes/admin'
11 |
12 | import log4js from 'log4js'
13 | import rateLimit from 'express-rate-limit'
14 |
15 | const app = express()
16 |
17 | app.enable('trust proxy')
18 |
19 | const createAccountLimiter = rateLimit({
20 | windowMs: 60 * 60 * 1000 * 24 * 7,
21 | max: 3,
22 | message: 'Too many accounts created from this IP! Please be nice!'
23 | })
24 |
25 | log4js.configure({
26 | appenders: {
27 | console: { type: 'console' },
28 | file: { type: 'file', filename: 'logs/log4jsconnect.log' }
29 | },
30 | categories: {
31 | default: { appenders: ['console'], level: 'info' },
32 | log4jslog: { appenders: ['file'], level: 'info' }
33 | }
34 | })
35 |
36 | const logger = log4js.getLogger('log4jslog')
37 |
38 | app.all('*', function(req, res, next) {
39 | res.header('Access-Control-Allow-Origin', '*')
40 | res.header('Access-Control-Allow-Headers', 'X-Requested-With')
41 | res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS')
42 | res.header('X-Powered-By', '3.2.1')
43 | res.header('Content-Type', 'application/json;charset=utf-8')
44 | next()
45 | })
46 |
47 | app.use(log4js.connectLogger(logger, { level: 'auto' }))
48 | app.use(bodyParser.json())
49 | app.use(bodyParser.urlencoded({ extended: false }))
50 |
51 | app.use('/', index)
52 | app.use('/users', users)
53 | app.use('/notes', notes)
54 | app.use('/modes', modes)
55 | app.use('/utils', util)
56 | app.use('/admin', admin)
57 |
58 | app.use('/users/code', createAccountLimiter)
59 |
60 | // catch 404 and forward to error handler
61 | app.use((req, res, next) => {
62 | const err = new Error('Not Found')
63 | err.status = 404
64 | next(err)
65 | })
66 |
67 | // error handler
68 | app.use((err, req, res) => {
69 | // set locals, only providing error in development
70 | res.locals.message = err.message
71 | res.locals.error = req.app.get('env') === 'development' ? err : {}
72 |
73 | // render the error page
74 | res.status(err.status || 500)
75 | res.render('error')
76 | })
77 |
78 | const server = http.createServer(app)
79 | server.listen('3002')
80 |
81 | module.exports = app
82 |
--------------------------------------------------------------------------------
/src/routes/admin.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 |
3 | import {
4 | MESSAGE,
5 | ADMIN_USER,
6 | ADMIN_PASSWORD,
7 | validate,
8 | JiGuangPush,
9 | } from '../config'
10 |
11 | import { User, Message } from '../models'
12 |
13 | const router = express.Router()
14 |
15 | // 反馈回复接口:推送+消息
16 | router.post('/reply', (req, res) => {
17 |
18 | const { account, password, uid, content } = req.body
19 | validate(res, false, uid, account, password, content)
20 |
21 | const response = async () => {
22 | if (account !== ADMIN_USER || password !== ADMIN_PASSWORD) {
23 | return res.jsonp(MESSAGE.PASSWORD_ERROR)
24 | }
25 |
26 | JiGuangPush(uid, content)
27 | await Message.create({
28 | title: content,
29 | type: 101,
30 | content: '',
31 | image: '',
32 | url: '',
33 | date: Date.now(),
34 | user_id: uid
35 | })
36 |
37 | return res.jsonp(MESSAGE.OK)
38 | }
39 |
40 | response()
41 | })
42 |
43 |
44 | // 用户列表接口
45 | router.get('/user/list', (req, res) => {
46 |
47 | const { account, password, page, limit } = req.body
48 | validate(res, false, uid, account, password, page, limit)
49 |
50 | const response = async () => {
51 | if (page <= 0 || limit < 1) {
52 | return res.jsonp(MESSAGE.REQUEST_ERROR)
53 | }
54 | if (account !== ADMIN_USER || password !== ADMIN_PASSWORD) {
55 | return res.jsonp(MESSAGE.PASSWORD_ERROR)
56 | }
57 | const users = await User.findAll({ offset: page * limit, limit })
58 | return res.jsonp({ ...MESSAGE.OK, data: users })
59 | }
60 |
61 | response()
62 | })
63 |
64 | // 修改匹配次数接口
65 | router.post('/user/last_times', (req, res) => {
66 |
67 | const { account, password, uid, last_times } = req.body
68 | validate(res, false, uid, account, password, last_times)
69 |
70 | const response = async () => {
71 |
72 | if (last_times < 0) {
73 | return res.jsonp(MESSAGE.REQUEST_ERROR)
74 | }
75 | if (account !== ADMIN_USER || password !== ADMIN_PASSWORD) {
76 | return res.jsonp(MESSAGE.PASSWORD_ERROR)
77 | }
78 |
79 | await User.update({ last_times }, { where: { id: uid } })
80 | return res.jsonp(MESSAGE.OK)
81 | }
82 |
83 | response()
84 | })
85 |
86 |
87 | // 修改会员时间接口
88 | router.post('/user/vip_expires', (req, res) => {
89 |
90 | const { account, password, uid, vip_expires, vip } = req.body
91 | validate(res, false, uid, account, password, vip_expires, vip)
92 |
93 | const response = async () => {
94 | if (account !== ADMIN_USER || password !== ADMIN_PASSWORD) {
95 | return res.jsonp(MESSAGE.PASSWORD_ERROR)
96 | }
97 | await User.update({ vip_expires, vip }, { where: { id: uid } })
98 | return res.jsonp(MESSAGE.OK)
99 | }
100 |
101 | response()
102 | })
103 |
104 | // 每月重置匹配次数接口
105 | router.get('/user/reset_last_times', (req, res) => {
106 |
107 | const { account, password } = req.query
108 | validate(res, false, account, password)
109 |
110 | const response = async () => {
111 | if (account !== ADMIN_USER || password !== ADMIN_PASSWORD) {
112 | return res.jsonp(MESSAGE.PASSWORD_ERROR)
113 | }
114 | await User.update({ last_times: 3 }, { where: { last_times: { lt: 3 } } })
115 | return res.jsonp(MESSAGE.OK)
116 | }
117 |
118 | response()
119 | })
120 |
121 | module.exports = router
122 |
--------------------------------------------------------------------------------
/src/config/index.js:
--------------------------------------------------------------------------------
1 | import md5 from 'md5'
2 | import { JPush } from 'jpush-async'
3 |
4 | const is_Production = false
5 |
6 | export const MESSAGE = {
7 | OK: {
8 | code: 0,
9 | message: '请求成功',
10 | },
11 | PASSWORD_ERROR: {
12 | code: 300,
13 | message: '密码错误',
14 | },
15 | ADMIN_ERROR: {
16 | code: 301,
17 | message: '管理员密码错误',
18 | },
19 | USER_EXIST: {
20 | code: 302,
21 | message: '用户已存在',
22 | },
23 | TOKEN_ERROR: {
24 | code: 403,
25 | message: 'TOKEN失效',
26 | },
27 | USER_NOT_EXIST: {
28 | code: 404,
29 | message: '用户不存在',
30 | },
31 | CODE_ERROR: {
32 | code: 405,
33 | message: '验证码错误',
34 | },
35 | CODE_NOT_EXIST: {
36 | code: 406,
37 | message: '邀请码失效或不存在',
38 | },
39 | PARAMETER_ERROR: {
40 | code: 422,
41 | message: '参数错误',
42 | },
43 | REQUEST_ERROR: {
44 | code: 501,
45 | message: '请求失败',
46 | },
47 | QUICK_REQUEST: {
48 | code: 502,
49 | message: '请求间隔过短',
50 | },
51 | CONNECT_ERROR_CLOSE: {
52 | code: 600,
53 | message: '用户已关闭匹配',
54 | },
55 | CONNECT_ERROR_ALREADY: {
56 | code: 601,
57 | message: '用户已匹配',
58 | },
59 | CONNECT_ERROR_BAN: {
60 | code: 602,
61 | message: '用户没有匹配权限'
62 | },
63 | CONNECT_ERROR_NO_TIME: {
64 | code: 603,
65 | message: '用户没有匹配次数'
66 | },
67 | CONNECT_ERROR_NO_NOTE: {
68 | code: 604,
69 | message: '用户没有写过日记'
70 | },
71 | CONNECT_ERROR_BAN_NOW: {
72 | code: 605,
73 | message: '用户没有匹配权限'
74 | },
75 | }
76 |
77 | export const KEY = ''
78 | export const SQL_USER = ''
79 | export const SQL_PASSWORD = ''
80 | export const YUNPIAN_APIKEY = '' // 云片APIKEY
81 | export const QINIU_ACCESS = '' // 七牛ACCESS
82 | export const QINIU_SECRET = '' // 七牛SECRET
83 | export const BUCKET = '' // 七牛BUCKET
84 | export const ADMIN_USER = ''
85 | export const ADMIN_PASSWORD = ''
86 | export const NLP_ID = ''
87 | export const NLP_SECRET = ''
88 | export const WXP_APPID = '' // 小程序 ID
89 | export const WXP_SECRET = '' // 小程序 KEY
90 | export const WX_APP_APPID = '' // 开放平台 APP ID
91 | export const WX_APP_APPSECRET = '' // 开放平台 APPSECRET
92 | export const GITHUB_TOKEN = '' // Github token
93 | export const QCLOUD_APPID = '' // 腾讯云 APPID
94 | export const QCLOUD_SECRETID = '' // 腾讯云 SECRETID
95 | export const QCLOUD_SECRETKEY = '' // 腾讯云 SECRETKEY
96 | export const WEBHOOK_KEY_WECHAT = '' // 企业微信“反馈喵@零熊” WebHook
97 | export const WEBHOOK_KEY_GITHUB = '' // 企业微信“Git喵@零熊” WebHook
98 |
99 | export const IS_CHECKING = false
100 |
101 | const JPUSH_KEY = ''
102 | const JPUSH_SECRET = ''
103 |
104 | const client = JPush.buildClient(JPUSH_KEY, JPUSH_SECRET)
105 |
106 | export const JiGuangPush = (user_id, message) => {
107 | client.push().setPlatform('ios', 'android')
108 | .setAudience(JPush.alias(user_id.toString()))
109 | .setNotification('双生日记', JPush.ios(message), JPush.android(message, null, 1))
110 | .setMessage(message)
111 | .setOptions(null, 60, null, is_Production)
112 | .send(function (err, res) {
113 | if (err) {
114 | if (err instanceof JPush.APIConnectionError) {
115 | console.log(err.message)
116 | // Response Timeout means your request to the server may have already received,
117 | // please check whether or not to push
118 | console.log(err.isResponseTimeout)
119 | } else if (err instanceof JPush.APIRequestError) {
120 | console.log(err.message)
121 | }
122 | } else {
123 | console.log('Sendno: ' + res.sendno)
124 | console.log('Msg_id: ' + res.msg_id)
125 | }
126 | })
127 | }
128 |
129 | export const md5Pwd = (password) => {
130 | const salt = 'Airing_is_genius_3957x8yza6!@#IUHJh~~'
131 | return md5(md5(password + salt))
132 | }
133 |
134 | export const validate = (res, check, ...params) => {
135 |
136 | for (let param of params) {
137 | if (typeof param === 'undefined') {
138 | return res.json(MESSAGE.PARAMETER_ERROR)
139 | }
140 | }
141 |
142 | if (check) {
143 | const uid = params[0]
144 | const timestamp = params[1]
145 | const token = params[2]
146 |
147 | if (token !== md5Pwd(uid.toString() + timestamp.toString() + KEY))
148 | return res.json(MESSAGE.TOKEN_ERROR)
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/docs/API.md:
--------------------------------------------------------------------------------
1 | # “双生” 服务端接口文档
2 |
3 | ## 1 用户模块 users
4 | ### 1.1 登录 login
5 | 接口:users/login
6 | 方式:POST
7 | 参数:account,password
8 | 响应:
9 | ```json
10 | {
11 | code: 0,
12 | message: 'success',
13 | data: {
14 | user: {},
15 | key: {},
16 | partner: {}
17 | }
18 | }
19 | ```
20 |
21 | ### 1.2 注册 register
22 | 接口:users/register
23 | 方式:POST
24 | 参数:account,password,code,timestamp
25 | 响应:
26 | ```json
27 | {
28 | code: 0,
29 | message: 'success',
30 | data: {
31 | user: {}
32 | }
33 | }
34 | ```
35 |
36 | ### 1.3 获取短信验证码 code
37 | 接口:users/code
38 | 方式:POST
39 | 参数:account
40 | 响应:
41 | ```json
42 | {
43 | code: 0,
44 | message: 'success',
45 | data: {
46 | timestamp: 15792391231
47 | }
48 | }
49 | ```
50 |
51 | ### 1.4 随机匹配 connect\_by\_random
52 | 接口:users/connect\_by\_random
53 | 方式:GET
54 | 参数:uid,timestamp,token
55 | 响应:
56 | ```json
57 | {
58 | code: 0,
59 | message: 'success',
60 | data: {}
61 | }
62 | ```
63 |
64 | ### 1.5 定向匹配 connect\_by\_id
65 | 接口:users/connect\_by\_id
66 | 方式:GET
67 | 参数:uid,timestamp,token,user\_id
68 | 响应:
69 | ```plain
70 | {
71 | code: 0,
72 | message: 'success',
73 | data: {}
74 | }
75 | ```
76 |
77 | ### 1.6 解除匹配 disconnect
78 | 接口:users/disconnect
79 | 方式:GET
80 | 参数:uid,timestamp,token
81 | 响应:
82 | ```json
83 | {
84 | code: 0,
85 | message: 'success'
86 | }
87 | ```
88 |
89 | ### 1.7 更新用户信息 update
90 | 接口:users/update
91 | 方式:POST
92 | 参数:uid,timestamp,token,name,face,sex
93 | 响应:
94 | ```json
95 | {
96 | code: 0,
97 | message: 'success'
98 | }
99 | ```
100 |
101 | ### 1.8 获取用户信息 user
102 | 接口:users/user
103 | 方式:GET
104 | 参数:uid,timestamp,token,user\_id
105 | 响应:
106 | ```json
107 | {
108 | code: 0,
109 | message: 'success',
110 | data: {}
111 | }
112 | ```
113 |
114 | ### 1.9 验证码登录注册接口 code\_login
115 | 接口:users/code\_login
116 | 方式:POST
117 | 参数:account,code
118 | 响应:
119 |
120 | ### 1.10 微信登录接口 oauth\_login
121 | 接口:users/oauth\_login
122 | 方式:POST
123 | 参数:
124 | 响应:
125 |
126 |
127 | ### 1.11 获取通知信息接口 show\_notification
128 | 接口:users/show\_notification
129 | 方式:GET
130 | 参数:uid,timestamp,token
131 | 响应:
132 | ```json
133 | {
134 | code: 0,
135 | message: 'success',
136 | data: []
137 | }
138 | ```
139 |
140 |
141 | ## 2. 日记模块 notes
142 | ### 2.1 发布日记 publish
143 | 接口:notes/publish
144 | 方式:POST
145 | 参数:uid,timestamp,token,title,content,images,latitude,longitude,mode
146 | 响应:
147 | ```json
148 | {
149 | code: 0,
150 | message: 'success'
151 | }
152 | ```
153 |
154 | ### 2.2 删除日记 delete
155 | 接口:notes/delete
156 | 方式:GET
157 | 参数:uid,timestamp,token,note\_id
158 | 响应:
159 | ```json
160 | {
161 | code: 0,
162 | message: 'success'
163 | }
164 | ```
165 |
166 | ### 2.3 点赞日记 like
167 | 接口:notes/like
168 | 方式:POST
169 | 参数:uid,timestamp,token,note\_id
170 | 响应:
171 | ```json
172 | {
173 | code: 0,
174 | message: 'success'
175 | }
176 | ```
177 |
178 |
179 | ### ~~2.5 随机推荐日记 recommend~~
180 | 说明:弃用,合并至notes/list :disappointed_relieved:
181 | 接口:notes/recommend
182 | 方式:GET
183 | 参数:uid,timestamp,token
184 | 响应:
185 |
186 | ### 2.6 获取日记列表 list
187 | 接口:notes/list
188 | 方式:GET
189 | 参数:uid,timestamp,token
190 | 响应:
191 | ```json
192 | {
193 | code: 0,
194 | message: 'success',
195 | data: {
196 | user: [],
197 | partner: [],
198 | recommend: {}
199 | }
200 | }
201 | ```
202 |
203 | ### 2.7 根据时间日记列表 show\_by\_time
204 | 接口:notes/show\_by\_time
205 | 方式:GET
206 | 参数:uid,timestamp,token,from\_timestamp
207 | 说明:本接口用于差额获取日记数据,from\_timestamp 为上次同步的时间。
208 | 响应:
209 | ```json
210 | {
211 | code: 0,
212 | message: 'success',
213 | data: {
214 | user: [],
215 | partner: [],
216 | recommend: {}
217 | }
218 | }
219 | ```
220 |
221 | ### 2.8 同步日记数据 sync
222 | 接口:notes/sync
223 | 方式:POST
224 | 参数:uid,timestamp,token,data
225 | 响应:
226 |
227 | ### 2.9 编辑日记 update
228 | 接口:notes/update
229 | 方式:POST
230 | 参数:uid,timestamp,token,note\_id,title,content,images,mode
231 | 响应:
232 | ```json
233 | {
234 | code: 0,
235 | message: 'success'
236 | }
237 | ```
238 |
239 |
240 | ## 3 情绪模块 modes
241 | ### ~~3.1 修改日记情绪值 update\_mode~~
242 | 说明:弃用,合并到 notes/update :disappointed_relieved:
243 | 接口:modes/update\_mode
244 | 方式:POST
245 | 参数:uid,timestamp,token,note\_id,mode
246 | 响应:
247 | ```json
248 | {
249 | code: 0,
250 | message: 'success'
251 | }
252 | ```
253 |
254 | ### 3.2 显示情绪图表 show
255 | 接口:modes/show
256 | 方式:GET
257 | 参数:uid,timestamp,token
258 | 响应:
259 |
260 | ## 4 工具模块 utils
261 | ### 4.1 请求上传图片接口 qiniu\_token
262 | 接口:utils/qiniu\_token
263 | 方式:GET
264 | 参数:uid,timestamp,token,filename
265 | 响应:
266 | ```json
267 | {
268 | code: 0,
269 | message: 'success',
270 | data: {}
271 | }
272 | ```
273 |
274 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/src/routes/util.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import qiniu from 'qiniu'
3 | import crypto from 'crypto'
4 |
5 | import {
6 | User,
7 | Message,
8 | Version
9 | } from '../models'
10 |
11 | import {
12 | QINIU_ACCESS,
13 | QINIU_SECRET,
14 | BUCKET,
15 | MESSAGE,
16 | ADMIN_USER,
17 | ADMIN_PASSWORD,
18 | validate,
19 | JiGuangPush,
20 | QCLOUD_APPID,
21 | QCLOUD_SECRETID,
22 | QCLOUD_SECRETKEY,
23 | NLP_ID,
24 | NLP_SECRET,
25 | WEBHOOK_KEY_GITHUB
26 | } from '../config/index'
27 |
28 | import { getAccessToken } from '../utils'
29 |
30 | import Promise from 'Promise'
31 |
32 | import rp from 'request-promise'
33 |
34 | // import Capi from 'qcloudapi-sdk'
35 | //
36 | // const capi = new Capi({
37 | // SecretId: NLP_ID,
38 | // SecretKey: NLP_SECRET,
39 | // serviceType: 'wenzhi'
40 | // })
41 |
42 | const tencentcloud = require('tencentcloud-sdk-nodejs')
43 |
44 | const NlpClient = tencentcloud.nlp.v20190408.Client
45 | const models = tencentcloud.nlp.v20190408.Models
46 |
47 | const Credential = tencentcloud.common.Credential
48 | const ClientProfile = tencentcloud.common.ClientProfile
49 | const HttpProfile = tencentcloud.common.HttpProfile
50 |
51 |
52 | const router = express.Router()
53 | qiniu.conf.ACCESS_KEY = QINIU_ACCESS
54 | qiniu.conf.SECRET_KEY = QINIU_SECRET
55 |
56 | /* 获取七牛token */
57 | router.get('/qiniu_token', (req, res) => {
58 |
59 | const {
60 | uid,
61 | timestamp,
62 | token,
63 | filename
64 | } = req.query
65 | validate(res, true, uid, timestamp, token, filename)
66 |
67 | const putPolicy = new qiniu.rs.PutPolicy(BUCKET + ':' + filename)
68 | const data = putPolicy.token()
69 |
70 | return res.json({
71 | ...MESSAGE.OK,
72 | data
73 | })
74 | })
75 |
76 | /* 获取 OCR 签名*/
77 | router.get('/get_ocr_sign', (req, res) => {
78 |
79 | const {
80 | uid,
81 | timestamp,
82 | token
83 | } = req.query
84 | validate(res, true, uid, timestamp, token)
85 |
86 | const currentTime = Math.round(Date.now() / 1000)
87 | const expiredTime = currentTime + 30 * 24 * 60 * 60
88 | const rand = Math.round(Math.random() * (2 ** 32))
89 | const origin = `a=${QCLOUD_APPID}&k=${QCLOUD_SECRETID}&e=${expiredTime}&t=${currentTime}&r=${rand}`
90 |
91 | const data = Buffer.from(origin, 'utf8')
92 | const signTmp = crypto.createHmac('sha1', QCLOUD_SECRETKEY).update(data).digest()
93 | const bin = Buffer.concat([signTmp, data])
94 | const sign = Buffer.from(bin).toString('base64')
95 |
96 | return res.json({
97 | ...MESSAGE.OK,
98 | data: sign
99 | })
100 | })
101 |
102 | /* NLP 情感分析接口 */
103 | router.post('/get_nlp_result', (req, res) => {
104 |
105 | const {
106 | uid,
107 | timestamp,
108 | token,
109 | content
110 | } = req.body
111 | validate(res, true, uid, timestamp, token)
112 |
113 | // 文档:https://cloud.tencent.com/document/product/271/35497
114 | const callApi = (action) => {
115 |
116 | return new Promise((resolve, reject) => {
117 |
118 | let cred = new Credential(NLP_ID, NLP_SECRET)
119 | let httpProfile = new HttpProfile()
120 | httpProfile.endpoint = 'nlp.tencentcloudapi.com'
121 | let clientProfile = new ClientProfile()
122 | clientProfile.httpProfile = httpProfile
123 | let client = new NlpClient(cred, 'ap-guangzhou', clientProfile)
124 |
125 | let params = { Text: content }
126 | let req
127 |
128 | // 内容敏感审核
129 | if (action === 'TextSensitivity') {
130 | req = new models.ContentApprovalRequest()
131 | req.from_json_string(JSON.stringify(params))
132 | client.ContentApproval(req, function (errMsg, response) {
133 | if (errMsg) {
134 | reject(errMsg)
135 | } else {
136 | resolve(response)
137 | }
138 | })
139 | }
140 |
141 | // 情感分析
142 | if (action === 'TextSentiment') {
143 | req = new models.SentimentAnalysisRequest()
144 | req.from_json_string(JSON.stringify(params))
145 | client.SentimentAnalysis(req, function (errMsg, response) {
146 | if (errMsg) {
147 | reject(errMsg)
148 | } else {
149 | resolve(response)
150 | }
151 | })
152 | }
153 | })
154 | }
155 |
156 | const response = async () => {
157 | const data = await callApi('TextSentiment')
158 | const { Positive: positive } = data
159 |
160 | const user = await User.findOne({
161 | where: {
162 | id: uid
163 | }
164 | })
165 |
166 | let total_notes = user.total_notes
167 | let total_modes = user.mode * total_notes
168 |
169 | if (user.emotions_type) {
170 | let emotions_basis = user.emotions_basis.split(',')
171 | let emotions_array = user.emotions.split(',')
172 | let [e_basis, c_basis, o_basis, a_basis, n_basis] = emotions_basis
173 | let [total_e, total_c, total_o, total_a, total_n] = emotions_array
174 |
175 | // e, c, o, a, n 取值范围是 0~1,需要从算法服务器的接口中获取
176 | // let { e, c, o, a, n } = data
177 | // 目前仅内部灰度测试,不对外使用
178 | let e = +((Math.random()).toFixed(8))
179 | let c = +((Math.random()).toFixed(8))
180 | let o = +((Math.random()).toFixed(8))
181 | let a = +((Math.random()).toFixed(8))
182 | let n = +((Math.random()).toFixed(8))
183 |
184 | if (uid === 2 || uid === 3) {
185 | const options = {
186 | method: 'POST',
187 | uri: 'http://118.24.154.90/ner',
188 | body: {
189 | content,
190 | key: NLP_ID
191 | },
192 | json: true
193 | }
194 | let mode = await rp(options)
195 | if (mode.code === 0) {
196 | e = mode.data.mood_sub_type.E
197 | c = mode.data.mood_sub_type.C
198 | a = mode.data.mood_sub_type.A
199 | n = mode.data.mood_sub_type.N
200 | o = mode.data.mood_sub_type.O
201 | }
202 | }
203 |
204 | e = (total_e * total_notes + e_basis * e) / (total_notes + 1)
205 | c = (total_c * total_notes + c_basis * c) / (total_notes + 1)
206 | o = (total_o * total_notes + o_basis * o) / (total_notes + 1)
207 | a = (total_a * total_notes + a_basis * a) / (total_notes + 1)
208 | n = (total_n * total_notes + n_basis * n) / (total_notes + 1)
209 |
210 | let emotions = e + ',' + c + ',' + o + ',' + a + ',' + n
211 |
212 | await User.update({
213 | total_notes: total_notes + 1,
214 | mode: Math.floor((total_modes + Math.floor(positive * 100)) / (total_notes + 1)),
215 | emotions
216 | }, { where: { id: uid } })
217 | } else {
218 |
219 | await User.update({
220 | total_notes: total_notes + 1,
221 | mode: Math.floor((total_modes + Math.floor(positive * 100)) / (total_notes + 1))
222 | }, { where: { id: uid } })
223 | }
224 |
225 | return res.json({
226 | ...MESSAGE.OK,
227 | data: positive
228 | })
229 | }
230 |
231 | response()
232 | })
233 |
234 | /* 获取并刷新心情报告接口 */
235 | router.get('/update_emotion_report', (req, res) => {
236 |
237 | const {
238 | uid,
239 | timestamp,
240 | token
241 | } = req.query
242 | validate(res, true, uid, timestamp, token)
243 |
244 | const response = async () => {
245 | let user = await User.findOne({
246 | where: {
247 | id: uid
248 | }
249 | })
250 |
251 | if (!user) {
252 | return res.json(MESSAGE.USER_NOT_EXIST)
253 | }
254 |
255 | if (!user.emotions || !user.emotions_type) {
256 | return res.json(MESSAGE.REQUEST_ERROR)
257 | }
258 |
259 | let emotions = user.emotions.split(',')
260 | let [e, c, o, a, n] = emotions
261 |
262 | let max = Math.max(e, c, o, a, n)
263 |
264 | let emotions_types, emotions_type, emotions_report = ''
265 | let tag = ''
266 |
267 | switch (max) {
268 | case +a:
269 | tag = 'n'
270 | emotions_types = ['实干主义者', '心灵多面手', '温和思想家', '自我笃行者']
271 | emotions_report = `为人谦逊温和,不爱与他人争论。在有意无意中可能会降低自己的原则或者标准,和温柔不同,温和是性格,温柔是态度。你是个温和的人,不爱计较,喜欢忍让,在忍让的过程中,可能会积攒起负能量灾区。一旦导火索被引燃,就容易陷入情绪爆炸。(情绪解读)
272 | 你在学业和事业方面是不温不火的,有自己的节奏,或快或慢,但都在自己的掌控当中,不爱跟他人作比较,觉得自己的事情不需要跟他人进行对比。你有一个属于自己的小宇宙。常常沉浸在自我的小世界中。你擅长进行独自深入地思考,常常会有异于常人的灵感迸发。温和的你可以适当的调整自己的步伐,跟随自己的心。心所向,意所达。(学业事业)
273 | 温和平静的性格可能帮助你在状态上达到平衡,健康的状态能维持很长时间。但在遇到突发事件时,还可以多增进自己的应激能力。同时可以去尝试新的事物,增长自己的见识和开拓眼界。做一个温文尔雅,内涵饱满的儒雅之士。(健康身心)`
274 | break
275 | case +e:
276 | tag = 'e'
277 | emotions_types = ['恬淡小天使', '温暖小甜心', '元气小青年', '品质小资']
278 | emotions_report = `你在工作或学习上尽心尽责、勤奋可靠,你认真、合理地安排自己的精力和时间,以便工作时的每一分钟都能够起到最大的作用,从而提高工作的效率和质量,你容易和同事同学建立良好的关系,获得更多的帮助和支持,但有时候过度地要求自己可能会让你陷入病态的完美主义和强迫行为的困境,“要么不做,要做就要做到最好”并不是一句好的座右铭。尝试着告诉自己——我们必须从整体上接纳和遵从我们生命的限制,然后寻找最佳的或是接近最佳的方式来分配我们的时间和精力。(学业)
279 | 你容易获得广泛的社会支持力量,一个丰富的、有支持感的社交生活可以有效降低心血管疾病、癌症的风险,且能使人有更长的寿命。归属感和社会联结可降低心理疾病的风险,增加生活意义感。与医生保持良好的沟通,较少出现酗酒或物质滥用等问题。(健康)
280 | 你对待学业和工作认真尽责的态度会对伴侣起到一个良好的促进作用,帮助TA也在自己的领域内获得成就。同时,细心体贴的你更容易悉心照料到伴侣的种种情绪,是个十足的小棉袄,乐于付出的你不妨常和伴侣交流对感情的期望更容易让彼此获得令双方都满意的亲密关系。(爱情)`
281 | break
282 | case +c:
283 | tag = 'c'
284 | emotions_types = ['躁动小魔王', '科学小怪人', '极致主义者', '暴躁领袖']
285 | emotions_report = `对生活抱有极大热忱的你,有时候难免会过度关注生活中负面的信息,尤其是与自身相关的方面,所以总是在一个又一个难以入眠的夜晚细细数着白天是否完成自己的计划、离自己的目标有没有更进一步……总是觉得自己没有达到理想中的自己。但正是反复的思考和担忧让你对目标和方向更加清晰明确,也提前对即将到来的困难做好准备。对风险和困难的敏感是你永不停歇的奋斗源泉。(学业)
286 | 尽管容易受到负面信息的影响,造成情绪波动,从而进行间歇性的暴饮暴食和抽烟喝酒,若是长时间陷入焦虑但是通常你对自己的健康状况非常警觉,身体上一点小小的问题也会让你警惕起来,去医院寻求治疗,所以重症疾病很容易被你扼杀在萌芽里。天性外向开朗的你更容易在遇到困境或是心情低落时寻求朋友的帮助。(健康)
287 | 虽然有时候神经敏感会让你过度解读伴侣的一言一行,例如TA的面无表情会让你认为是一种冷漠无奈的抵抗。但是你会更加容易和伴侣建立起沟通机制。在沟通这件事上,我们总是误以为自己的非言语线索足够让对方明白我们想表达的意思,但其实,不论在一起多久、多么有默契的伴侣也通常难以做到这一点,这时候需要我们冷静下来,把思绪仔细地告诉对方。(爱情)`
288 | break
289 | case +o:
290 | tag = 'o'
291 | emotions_types = ['厌世大魔王', '灵性创作家', '小世界掌控家', '灵魂多面手']
292 | emotions_report = `大到漫漫一生,小到一天的安排,你总是对此小心翼翼提心吊胆,似乎失去一丁点的掌控都足以让你窒息抓狂。你很容易受到外界的影响而产生较大的情绪波动,对负面信息比较在意,你经常反复思考和担忧。但也正是这思考,让你比常人更多一份创造力。(情绪解读)
293 | 你在学业和事业方面一定是一个相当有创造力的人,你擅长从细节处不断进行深入地思考,从而能够触类旁通不断进行发散,在现有结论的基础上进行再创造。但是有时候不必在细节处过于纠结,而是学会放眼全局,说不定能收获更加开阔的视野。(学业事业)
294 | 更加开放的性格可能帮助你在心理上保持健康,心理上的健康不仅指更加积极乐观、对压力的处理能力更强,而且更加容易让你保持健康的饮食和运动习惯。同时你愿意去尝试新的事物,寻求新异和多样性、尝试新的活动更加有利于你在经历创伤事件后的恢复,保持平和开放的心态。(健康身心)`
295 | break
296 | case +n:
297 | tag = 'n'
298 | emotions_types = ['忧郁小王子', '忧伤小绵羊', '谦和小智者', '忧郁小麋鹿']
299 | emotions_report = `为人低调谦和,虽然不常生气,但是也没有能很好地控制自己的情绪。你经常需要治愈系的聆听者,希望将自己的心事告诉挚友。你身上总是有温暖的光亮。美好的事物各不相同,世界也瞬息万变,而你是一颗永恒的星星,因为自身会发出温暖而明亮的光芒,所以你不惧怕黑暗,你是如此美好,让恐惧烟消云散,你是这个星球上的一点希望。(情绪解读)
300 | 你在学业和事业方面一定是一个相当有潜力的人,不论是脑海里还是胸腔中,都藏着一个大大的宇宙。你擅长从细节处不断进行深入地思考,从而能够触类旁通不断进行发散,在现有结论的基础上进行再创造。但是有时候不必在细节处过于纠结,而是学会放眼全局,说不定能收获更加开阔的视野。(学业事业)
301 | 更加开放的性格可能帮助你在心理上保持健康,心理上的健康不仅指更加积极乐观、对压力的处理能力更强,而且更加容易让你保持健康的饮食和运动习惯。同时你愿意去尝试新的事物,寻求新异和多样性、尝试新的活动更加有利于你在经历创伤事件后的恢复,保持平和开放的心态。(健康身心)`
302 | break
303 | default:
304 | emotions_types = []
305 | break
306 | }
307 |
308 | if (emotions_types.indexOf(user.emotions_type) !== -1) {
309 | return res.json({
310 | ...MESSAGE.OK,
311 | data: user
312 | })
313 | } else {
314 |
315 | let type_id = Math.floor(Math.random() * 4)
316 | emotions_type = emotions_types[type_id]
317 | let emotions_url = tag + type_id
318 |
319 | await User.update({
320 | emotions_type,
321 | emotions_report
322 | }, { where: { id: uid } })
323 | user = await User.findOne({
324 | where: {
325 | id: uid
326 | }
327 | })
328 | return res.json({
329 | ...MESSAGE.OK,
330 | data: {
331 | ...user,
332 | emotions_url
333 | }
334 | })
335 | }
336 | }
337 |
338 | response()
339 | })
340 |
341 | router.get('/show_act', (req, res) => {
342 |
343 | const {
344 | uid,
345 | timestamp,
346 | token
347 | } = req.query
348 | validate(res, true, uid, timestamp, token)
349 |
350 | // url: 活动主页, shareUrl: 分享页面
351 | return res.json({
352 | ...MESSAGE.OK,
353 | show: true,
354 | url: 'https://2life.act.ursb.me/#/',
355 | shareUrl: 'https://2life.act.ursb.me/#/invitation'
356 | })
357 | })
358 |
359 | /* 小程序获取access_token
360 | * 文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140183
361 | */
362 | router.get('/access_token', (req, res) => {
363 | const {
364 | uid,
365 | timestamp,
366 | token
367 | } = req.query
368 | validate(res, true, uid, timestamp, token)
369 |
370 | const response = async () => {
371 |
372 | const access_token = await getAccessToken()
373 |
374 | return res.json({
375 | ...MESSAGE.OK,
376 | data: {
377 | access_token,
378 | timestamp: Date.now()
379 | }
380 | })
381 | }
382 | response()
383 | })
384 |
385 | /* 后台发送通知 */
386 | router.get('/push_message', (req, res) => {
387 | JiGuangPush(1, '您被另一半解除匹配了:(,多写日记来记录自己的生活吧!')
388 |
389 | const response = async () => {
390 | await Message.create({
391 | title: 'Airing 成功匹配到了您,成为您的另一半',
392 | type: 201,
393 | content: '',
394 | image: '',
395 | url: '',
396 | date: Date.now(),
397 | user_id: 1
398 | })
399 | return res.json(MESSAGE.OK)
400 | }
401 | response()
402 | })
403 |
404 | /* 备份服务端日志 */
405 | router.post('/save_logs', (req, res) => {
406 |
407 | const {
408 | admin,
409 | password
410 | } = req.body
411 |
412 | validate(res, false, admin, password)
413 |
414 | const response = async () => {
415 |
416 | if (admin === ADMIN_USER && password === ADMIN_PASSWORD) {
417 | return res.json(MESSAGE.OK)
418 | }
419 | }
420 |
421 | response()
422 | })
423 |
424 | /* 图片安全接口回调 */
425 | router.get('/ban_img', (_, res) => {
426 | const response = async () => {
427 | return res.json(MESSAGE.OK)
428 | }
429 | response()
430 | })
431 |
432 | /* 判断机型与重定向到商店 */
433 | router.get('/store', (req, res) => {
434 | let ua = req.headers['user-agent']
435 |
436 | if (/Android/.test(ua)) {
437 | res.location('https://www.coolapk.com/apk/214311')
438 | } else {
439 | res.location('https://itunes.apple.com/cn/app/%E5%8F%8C%E7%94%9F%E6%97%A5%E8%AE%B0-%E4%BD%A0%E6%98%AF%E6%88%91%E6%97%A5%E8%AE%B0%E9%87%8C%E5%86%99%E4%B8%8B%E7%9A%84%E4%B8%83%E5%A4%95/id1245100877?mt=8')
440 | }
441 | res.send(302)
442 | })
443 |
444 | /* 获取当前平台最新稳定版版本号 */
445 | router.get('/version', (req, res) => {
446 |
447 | const {
448 | is_wxapp
449 | } = req.query
450 |
451 | let ua = req.headers['user-agent']
452 | const response = async () => {
453 | let data = []
454 | if (is_wxapp === 0) {
455 | if (/Android/.test(ua)) {
456 | data = await Version.findAll({
457 | where: {
458 | platform: 2,
459 | status: 1
460 | }
461 | })
462 | }
463 |
464 | if (/like Mac OS X/.test(ua)) {
465 | data = await Version.findAll({
466 | where: {
467 | platform: 1,
468 | status: 1
469 | }
470 | })
471 | }
472 | } else {
473 | data = await Version.findAll({
474 | where: {
475 | platform: 3,
476 | status: 1
477 | }
478 | })
479 | }
480 | const version = data[0]
481 | return res.json({ ...MESSAGE.OK, data: version })
482 | }
483 |
484 | response()
485 | })
486 |
487 | /* 检测某版本号是否过期 */
488 | router.get('/check_version', (req, res) => {
489 |
490 | const {
491 | version,
492 | platform
493 | } = req.query
494 |
495 | const response = async () => {
496 | let data = await Version.findOne({
497 | where: {
498 | version,
499 | platform,
500 | status: 0
501 | }
502 | })
503 |
504 | if (data) {
505 | return res.json({ ...MESSAGE.OK, data })
506 | } else {
507 | return res.json(MESSAGE.CODE_ERROR)
508 | }
509 | }
510 |
511 | response()
512 | })
513 |
514 | router.post('/git_webhook', (req, res) => {
515 | const { secret } = req.query
516 | const { sender: { login } } = req.body
517 |
518 | if (secret !== WEBHOOK_KEY_GITHUB) return
519 |
520 | const response = async () => {
521 |
522 | let webhookOptions = {
523 | uri: `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${WEBHOOK_KEY_GITHUB}`,
524 | method: 'POST',
525 | body: {
526 | msgtype: 'text',
527 | markdown: {
528 | content: `${login} 刚刚提交了代码,请相关成员注意。`,
529 | }
530 | },
531 | json: true
532 | }
533 | await rp(webhookOptions)
534 |
535 | return res.json(MESSAGE.OK)
536 | }
537 |
538 | response()
539 |
540 | })
541 |
542 | router.get('/git_webhook', (req, res) => {
543 | const { secret } = req.query
544 | const { sender: { login } } = req.body
545 |
546 | if (secret !== WEBHOOK_KEY_GITHUB) return
547 |
548 | const response = async () => {
549 |
550 | let webhookOptions = {
551 | uri: `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${WEBHOOK_KEY_GITHUB}`,
552 | method: 'POST',
553 | body: {
554 | msgtype: 'text',
555 | markdown: {
556 | content: `${login} 刚刚提交了代码,请相关成员注意。`,
557 | }
558 | },
559 | json: true
560 | }
561 | await rp(webhookOptions)
562 |
563 | return res.json(MESSAGE.OK)
564 | }
565 |
566 | response()
567 |
568 | })
569 |
570 | module.exports = router
571 |
--------------------------------------------------------------------------------
/src/routes/notes.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 | import fetch from 'node-fetch'
3 |
4 | import { User, Note, Message, Comment, Report } from '../models'
5 |
6 | import {
7 | MESSAGE,
8 | validate,
9 | JiGuangPush,
10 | NLP_ID,
11 | NLP_SECRET
12 | } from '../config'
13 |
14 | import { wechatContentCheck, wechatImgCheck } from '../utils'
15 |
16 | import Promise from 'Promise'
17 |
18 | const tencentcloud = require('tencentcloud-sdk-nodejs')
19 |
20 | const NlpClient = tencentcloud.nlp.v20190408.Client
21 | const models = tencentcloud.nlp.v20190408.Models
22 |
23 | const Credential = tencentcloud.common.Credential
24 | const ClientProfile = tencentcloud.common.ClientProfile
25 | const HttpProfile = tencentcloud.common.HttpProfile
26 |
27 | const router = express.Router()
28 |
29 | /* notes/publish
30 | * 该接口弃用,仅用于兼容2.0.8以下版本(20180726)
31 | */
32 | router.post('/publish', (req, res) => {
33 |
34 | const {
35 | uid,
36 | token,
37 | timestamp,
38 | title,
39 | content,
40 | location,
41 | longitude,
42 | latitude,
43 | images
44 | } = req.body
45 |
46 | validate(
47 | res,
48 | true,
49 | uid,
50 | timestamp,
51 | token,
52 | title,
53 | content,
54 | location,
55 | longitude,
56 | latitude,
57 | images)
58 |
59 | // 文档:https://cloud.tencent.com/document/product/271/35497
60 | const callApi = (action) => {
61 |
62 | return new Promise((resolve, reject) => {
63 |
64 | let cred = new Credential(NLP_ID, NLP_SECRET)
65 | let httpProfile = new HttpProfile()
66 | httpProfile.endpoint = 'nlp.tencentcloudapi.com'
67 | let clientProfile = new ClientProfile()
68 | clientProfile.httpProfile = httpProfile
69 | let client = new NlpClient(cred, 'ap-guangzhou', clientProfile)
70 |
71 | let params = {Text: `${title}.${content}`}
72 | let req
73 |
74 | // 内容敏感审核
75 | if (action === 'TextSensitivity') {
76 | req = new models.ContentApprovalRequest()
77 | req.from_json_string(JSON.stringify(params))
78 | client.ContentApproval(req, function (errMsg, response) {
79 | if (errMsg) {
80 | reject(errMsg)
81 | } else {
82 | resolve(response)
83 | }
84 | })
85 | }
86 |
87 | // 情感分析
88 | if (action === 'TextSentiment') {
89 | req = new models.SentimentAnalysisRequest()
90 | req.from_json_string(JSON.stringify(params))
91 | client.SentimentAnalysis(req, function (errMsg, response) {
92 | if (errMsg) {
93 | reject(errMsg)
94 | } else {
95 | resolve(response)
96 | }
97 | })
98 | }
99 | })
100 | }
101 |
102 | const response = async () => {
103 | const user = await User.findOne({ where: { id: uid } })
104 |
105 | const note = await Note.findOne({
106 | where: {
107 | user_id: uid,
108 | title,
109 | content,
110 | date: {
111 | 'gte': (req.body.date || Date.now()) - 10000,
112 | 'lte': (req.body.date || Date.now()) + 10000
113 | }
114 | }
115 | })
116 |
117 | if (note) {
118 | res.json({ code: 504 })
119 | return
120 | }
121 |
122 | const { EvilKeywords } = await callApi('TextSensitivity')
123 |
124 | const userAgent = req['headers']['user-agent'].toLowerCase()
125 | if (userAgent.match('micromessenger') !== null || userAgent.match('wechatdevtool') !== null) {
126 | console.log('user-agent:', userAgent)
127 |
128 | const msgSecCheck = await wechatContentCheck(title + '-' + content)
129 | const imgSecCheck = await wechatImgCheck(images)
130 |
131 | if (EvilKeywords || msgSecCheck.errcode !== 0 || imgSecCheck.errcode !== 0) {
132 | return res.json(MESSAGE.REQUEST_ERROR)
133 | }
134 | } else {
135 | if (EvilKeywords) {
136 | return res.json(MESSAGE.REQUEST_ERROR)
137 | }
138 | }
139 |
140 | const data = await callApi('TextSentiment')
141 | const { Positive } = data
142 |
143 | await Note.create({
144 | user_id: uid,
145 | title,
146 | content,
147 | images,
148 | longitude,
149 | latitude,
150 | location,
151 | is_liked: 0,
152 | mode: req.body.mode || Math.floor(Positive * 100),
153 | date: req.body.date || Date.now(),
154 | status: req.body.status || user.status
155 | })
156 |
157 | // let total_notes = user.total_notes
158 | // let total_modes = user.mode * total_notes
159 | //
160 | // await User.update({
161 | // total_notes: total_notes + 1,
162 | // mode: Math.floor((total_modes + Math.floor(positive * 100)) / (total_notes + 1))
163 | // }, { where: { id: uid } })
164 |
165 | return res.json(MESSAGE.OK)
166 | }
167 |
168 | response()
169 | })
170 |
171 | /* notes/delete */
172 | router.get('/delete', (req, res) => {
173 |
174 | const { uid, timestamp, token, note_id } = req.query
175 | validate(res, true, uid, timestamp, token, note_id)
176 |
177 | const response = async () => {
178 | const user = await User.findOne({ where: { id: uid } })
179 | await Note.destroy({ where: { id: note_id } })
180 | await user.decrement('total_notes')
181 | return res.json(MESSAGE.OK)
182 | }
183 |
184 | response()
185 | })
186 |
187 | /* notes/like */
188 | router.post('/like', (req, res) => {
189 |
190 | const { uid, timestamp, token, note_id } = req.body
191 | validate(res, true, uid, timestamp, token, note_id)
192 |
193 | const response = async () => {
194 | const user = await User.findOne({ where: { id: uid } })
195 | const partner = await User.findOne({ where: { id: user.user_other_id } })
196 | await Note.update({ is_liked: 1 }, { where: { id: note_id } })
197 | // 通知对方被喜欢
198 | JiGuangPush(user.user_other_id, `${user.name} 喜欢了你的日记,真是幸福的一天`)
199 | await Message.create({
200 | title: `${user.name} 喜欢了你的日记,真是幸福的一天`,
201 | type: 203,
202 | content: '',
203 | image: '',
204 | url: '',
205 | date: Date.now(),
206 | user_id: partner.id
207 | })
208 | await partner.increment('unread')
209 |
210 | return res.json(MESSAGE.OK)
211 | }
212 |
213 | response()
214 | })
215 |
216 | /* notes/list */
217 | router.get('/list', (req, res) => {
218 |
219 | const { uid, timestamp, token } = req.query
220 | validate(res, true, uid, timestamp, token)
221 |
222 | const response = async () => {
223 | let user = await Note.findAll({ where: { user_id: uid } })
224 | let partner = []
225 | let recommend = {}
226 |
227 | const u = await User.findOne({ where: { id: uid } })
228 |
229 | if (u.user_other_id !== -1) {
230 | // 已匹配
231 | partner = await Note.findAll({ where: { user_id: u.user_other_id } })
232 | } else if (u.status < 200) {
233 | // 希望匹配异性,但是未匹配
234 | let recommends = []
235 | if (u.sex === 0) {
236 | recommends = await Note.findAll({
237 | where: {
238 | status: { 'gte': 110, 'lt': 120 },
239 | date: { 'gte': new Date().setHours(0, 0, 0, 0), 'lt': new Date().setHours(0, 0, 0, 0) + 86400000 }
240 | }
241 | })
242 | } else {
243 | recommends = await Note.findAll({
244 | where: {
245 | status: { 'gte': 100, 'lt': 110 },
246 | date: { 'gte': new Date().setHours(0, 0, 0, 0), 'lt': new Date().setHours(0, 0, 0, 0) + 86400000 }
247 | }
248 | })
249 | }
250 | if (recommends[0]) {
251 | recommend = recommends[Math.floor(Math.random() * recommends.length)]
252 | }
253 | } else if (u.status > 200 && u.status < 300) {
254 | // 希望匹配同姓,但是未匹配
255 | let recommends = []
256 | if (u.sex === 0) {
257 | recommends = await Note.findAll({
258 | where: {
259 | status: { 'gte': 200, 'lt': 210 },
260 | date: { 'gte': new Date().setHours(0, 0, 0, 0), 'lt': new Date().setHours(0, 0, 0, 0) + 86400000 }
261 | }
262 | })
263 | } else {
264 | recommends = await Note.findAll({
265 | where: {
266 | status: { 'gte': 210, 'lt': 220 },
267 | date: { 'gte': new Date().setHours(0, 0, 0, 0), 'lt': new Date().setHours(0, 0, 0, 0) + 86400000 }
268 | }
269 | })
270 | }
271 | if (recommends[0]) {
272 | recommend = recommends[Math.floor(Math.random() * recommends.length)]
273 | }
274 | }
275 |
276 | return res.json({
277 | ...MESSAGE.OK,
278 | data: { user, partner, recommend }
279 | })
280 | }
281 |
282 | response()
283 | })
284 |
285 | /* notes/show_by_time */
286 | router.get('/show_by_time', (req, res) => {
287 |
288 | const { uid, timestamp, token, from_time } = req.query
289 | validate(res, true, uid, timestamp, token, from_time)
290 |
291 | const response = async () => {
292 | return res.json({ ...MESSAGE.OK, data })
293 | }
294 |
295 | response()
296 | })
297 |
298 | /* notes/sync */
299 | router.get('/sync', (req, res) => {
300 |
301 | const { uid, timestamp, token, synctime } = req.query
302 | validate(res, true, uid, timestamp, token, synctime)
303 |
304 | const response = async () => {
305 |
306 | // 从七牛云上拉取json文件
307 | const response = await fetch(`https://airing.ursb.me/2life/file/${uid}_${synctime}.json`)
308 | const json = await response.json()
309 |
310 | // 根据操作符op同步数据库
311 | // 0: 未改动,1: 增加,2: 更改,3: 删除
312 | const diaryList = json.diaryList
313 | let diaryListAdd = diaryList.filter(item => {
314 | return item.op === 1
315 | })
316 |
317 | let diaryListUpdate = diaryList.filter(item => {
318 | return item.op === 2
319 | })
320 |
321 | let diaryListDelete = diaryList.filter(item => {
322 | return item.op === 3
323 | })
324 |
325 |
326 | if (diaryListAdd && diaryListAdd.length > 0) {
327 | await Note.bulkCreate(diaryListAdd)
328 | }
329 |
330 | if (diaryListUpdate && diaryListUpdate.length > 0) {
331 | for (let i = 0; i < diaryListUpdate.length; i++) {
332 | await Note.update(diaryListUpdate[i], { where: { id: diaryListUpdate[i].id } })
333 | }
334 | }
335 |
336 | if (diaryListDelete && diaryListDelete.length > 0) {
337 | for (let i = 0; i < diaryListDelete.length; i++) {
338 | await Note.destroy({ where: { id: diaryListDelete[i].id } })
339 | }
340 | }
341 |
342 | const data = await Note.findAll({ where: { user_id: uid } })
343 |
344 | return res.json({ ...MESSAGE.OK, data })
345 | }
346 |
347 | response()
348 | })
349 |
350 | /* notes/update */
351 | router.post('/update', (req, res) => {
352 |
353 | const { uid, timestamp, token, note_id, title, content, images, mode } = req.body
354 | validate(res, true, uid, timestamp, token, note_id, title, content, images, mode)
355 |
356 | const response = async () => {
357 |
358 | const userAgent = req['headers']['user-agent'].toLowerCase()
359 | if (userAgent.match('micromessenger') !== null || userAgent.match('wechatdevtool') !== null) {
360 | console.log('user-agent:', userAgent)
361 |
362 | const msgSecCheck = await wechatContentCheck(title + '-' + content)
363 | const imgSecCheck = await wechatImgCheck(images)
364 |
365 | if (msgSecCheck.errcode !== 0 || imgSecCheck.errcode !== 0) {
366 | return res.json(MESSAGE.REQUEST_ERROR)
367 | }
368 | }
369 |
370 | const user = await User.findOne({ where: { id: uid } })
371 | await Note.update({ title, content, images, mode: Math.floor(mode) }, { where: { id: note_id } })
372 |
373 | let total_notes = user.total_notes
374 | let total_modes = user.mode * total_notes
375 |
376 | await User.update({
377 | mode: Math.floor((total_modes + mode) / (total_notes + 1))
378 | }, { where: { id: uid } })
379 |
380 | return res.json(MESSAGE.OK)
381 | }
382 |
383 | response()
384 | })
385 |
386 | /* notes/refresh_total_notes */
387 | router.get('/refresh_total_notes', (req, res) => {
388 |
389 | const { uid, timestamp, token } = req.query
390 | validate(res, true, uid, timestamp, token)
391 |
392 | const response = async () => {
393 | const c = await Note.count({ where: { user_id: uid } })
394 |
395 | await User.update({
396 | total_notes: c
397 | }, { where: { id: uid } })
398 |
399 | return res.json(MESSAGE.OK)
400 | }
401 |
402 | response()
403 | })
404 |
405 | /*
406 | * 评论功能相关接口
407 | *
408 | * 1. 查询评论:notes/show_comment
409 | * 2. 添加评论:notes/add_comment
410 | * 3. TODO: 删除评论:notes/delete_comment
411 | */
412 |
413 | // 查询评论
414 | // 注:仅显示自己与日记主人的评论,如果是主人,显示全部评论
415 | router.get('/show_comment', (req, res) => {
416 | const { uid, timestamp, token, note_id, owner_id } = req.query
417 | validate(res, true, uid, timestamp, token, note_id, owner_id)
418 |
419 | const response = async () => {
420 |
421 | let comments = []
422 | if (uid === owner_id) {
423 | // 主人可以看到全部评论
424 | comments = await Comment.findAll({
425 | where: {
426 | note_id,
427 | delete: 0
428 | }, include: [
429 | { model: User, attributes: ['id', 'name', 'sex', 'face', 'status'], as: 'user' },
430 | { model: User, attributes: ['id', 'name', 'sex', 'face', 'status'], as: 'reply' }]
431 | })
432 | } else {
433 | // 客人只能看到自己与主人之间的评论
434 | comments = await Comment.findAll({
435 | where: {
436 | user_id: {
437 | '$in': [uid, owner_id]
438 | },
439 | reply_id: {
440 | '$in': [uid, owner_id]
441 | },
442 | note_id,
443 | delete: 0
444 | }, include: [
445 | { model: User, attributes: ['id', 'name', 'sex', 'face', 'status'], as: 'user' },
446 | { model: User, attributes: ['id', 'name', 'sex', 'face', 'status'], as: 'reply' }]
447 | })
448 | }
449 |
450 | return res.json({
451 | ...MESSAGE.OK,
452 | comments
453 | })
454 | }
455 |
456 | response()
457 | })
458 |
459 | // 添加评论
460 | router.post('/add_comment', (req, res) => {
461 |
462 | // uid: 评论者 id
463 | // user_id: 被回复者 id,如果 user_id 等于 uid,代表评论者是公开发信息
464 | // owner_id: 日记主人 id
465 | const { uid, timestamp, token, note_id, user_id, content, owner_id } = req.body
466 | validate(res, true, uid, timestamp, token, note_id, user_id, content, owner_id)
467 |
468 | const response = async () => {
469 |
470 | const user = await User.findOne({ where: { id: uid } }) // 评论者
471 |
472 | await Comment.create({
473 | note_id,
474 | user_id: uid,
475 | reply_id: user_id,
476 | content,
477 | delete: 0,
478 | date: Date.now()
479 | })
480 |
481 | if (uid !== user_id) {
482 | const partner = await User.findOne({ where: { id: user_id } }) // 被评论者
483 |
484 | // 通知对方被回复
485 | JiGuangPush(user_id, `${user.name} 回复了你的评论`)
486 | await Message.create({
487 | title: `${user.name} 回复了你的评论`,
488 | type: 203,
489 | content: '',
490 | image: '',
491 | url: '',
492 | date: Date.now(),
493 | user_id
494 | })
495 | await partner.increment('unread')
496 | } else {
497 | // 公开发有两种情况:
498 | // 1. 主人评论,不通知
499 | // 2. 客人评论,通知
500 | // 此处处理情况2
501 | if (uid !== owner_id) {
502 |
503 | const owner = await User.findOne({ where: { id: owner_id } }) // 主人
504 |
505 | // 通知主人被评论
506 | JiGuangPush(owner_id, `${user.name} 评论了你的日记,真是幸福的一天`)
507 | await Message.create({
508 | title: `${user.name} 评论了你的日记,真是幸福的一天`,
509 | type: 203,
510 | content: '',
511 | image: '',
512 | url: '',
513 | date: Date.now(),
514 | user_id: owner_id
515 | })
516 | await owner.increment('unread')
517 | }
518 | }
519 |
520 | return res.json(MESSAGE.OK)
521 | }
522 |
523 | response()
524 | })
525 |
526 | /*
527 | * 树洞功能相关接口
528 | *
529 | * 1. 获取树洞列表:notes/show_holes
530 | * 2. 举报树洞:notes/report_hole
531 | * 3. TODO: 获取匿名评论:notes/show_hole_comments
532 | */
533 |
534 | // 获取树洞列表
535 | router.get('/show_holes', (req, res) => {
536 | const { uid, timestamp, token, version } = req.query
537 | validate(res, true, uid, timestamp, token, version)
538 |
539 | const response = async () => {
540 |
541 | let data = []
542 |
543 | if (version !== '2.2.1') {
544 | const { pageIndex, pageSize } = req.query
545 | data = await Note.findAll({
546 | where: {
547 | hole_alive: {
548 | 'gte': Date.now()
549 | }
550 | },
551 | order: 'date DESC',
552 | offset: pageIndex * pageSize,
553 | limit: +pageSize,
554 | include: [{ model: User, attributes: ['id', 'code', 'name', 'sex', 'face', 'status', 'emotions_type'] }]
555 | })
556 | } else {
557 | // 2.2.1 版本未做分页处理
558 | data = await Note.findAll({
559 | where: {
560 | hole_alive: {
561 | 'gte': Date.now()
562 | }
563 | },
564 | order: 'date DESC',
565 | include: [{ model: User, attributes: ['id', 'code', 'name', 'sex', 'face', 'status', 'emotions_type'] }]
566 | })
567 | }
568 |
569 | return res.json({
570 | ...MESSAGE.OK,
571 | data
572 | })
573 | }
574 |
575 | response()
576 | })
577 |
578 | router.get('/report_hole', (req, res) => {
579 | const { uid, timestamp, token, note_id } = req.query
580 | validate(res, true, uid, timestamp, token, note_id)
581 |
582 | const response = async () => {
583 | await Report.create({
584 | note_id,
585 | user_id: uid,
586 | pass: 0
587 | })
588 | }
589 |
590 | response()
591 | })
592 |
593 | module.exports = router
594 |
--------------------------------------------------------------------------------
/src/routes/users.js:
--------------------------------------------------------------------------------
1 | import express from 'express'
2 |
3 | import { User, Code, Message, Note, Badge, Feedback, Activity } from '../models'
4 |
5 | import md5 from 'md5'
6 |
7 | import https from 'https'
8 | import querystring from 'querystring'
9 | import rp from 'request-promise'
10 |
11 | import {
12 | MESSAGE,
13 | KEY,
14 | YUNPIAN_APIKEY,
15 | WXP_APPID,
16 | WXP_SECRET,
17 | WX_APP_APPID,
18 | WX_APP_APPSECRET,
19 | GITHUB_TOKEN,
20 | validate,
21 | md5Pwd,
22 | JiGuangPush,
23 | IS_CHECKING,
24 | WEBHOOK_KEY_WECHAT
25 | } from '../config'
26 |
27 | const router = express.Router()
28 |
29 | /* users/code */
30 | router.post('/code', (req, res) => {
31 |
32 | const { account } = req.body
33 | const region = req.body.region || 'china'
34 | validate(res, false, account)
35 |
36 | const now = Date.now()
37 | const code = Math.floor(Math.random() * 8999 + 1000)
38 |
39 | const postData = {
40 | mobile: account,
41 | text: region === 'china' ? ('【双生日记】您的验证码是' + code) : ('【2Life】Your SMS Verification Code: ' + code),
42 | apikey: YUNPIAN_APIKEY
43 | }
44 |
45 | const content = querystring.stringify(postData)
46 | const options = {
47 | host: 'sms.yunpian.com',
48 | path: '/v2/sms/single_send.json',
49 | method: 'POST',
50 | agent: false,
51 | rejectUnauthorized: false,
52 | headers: {
53 | 'Content-Type': 'application/x-www-form-urlencoded',
54 | 'Content-Length': content.length
55 | }
56 | }
57 |
58 | const model = {
59 | account,
60 | code,
61 | timestamp: now,
62 | used: false
63 | }
64 |
65 | const sendMsg = async () => {
66 | const req = https.request(options, (res) => {
67 | res.setEncoding('utf8')
68 | })
69 | req.write(content)
70 | req.end()
71 | return true
72 | }
73 |
74 | const response = async () => {
75 | const results = await Code.findAll({ where: { account, used: false } })
76 | if (results[0] !== undefined) {
77 | if (now - results[0].timestamp < 60000) {
78 | return res.json(MESSAGE.REQUEST_ERROR)
79 | }
80 | }
81 | await Code.create(model)
82 | await sendMsg() // TODO: 取消本行注释
83 | return res.json({ ...MESSAGE.OK, data: { timestamp: now } })
84 | }
85 |
86 | response()
87 | })
88 |
89 | /* users/register */
90 | router.post('/register', (req, res) => {
91 |
92 | const { account, password, code, timestamp } = req.body
93 | validate(res, false, account, password, code, timestamp)
94 |
95 | const findCode = async () => {
96 | return await Code.findOne({ where: { code, timestamp } })
97 | }
98 |
99 | const response = async () => {
100 | const code = await findCode()
101 | if (code) {
102 | const user = await User.findOne({ where: { account } })
103 | // TODO: 未知 bug
104 | // await Code.update({ used: true }, { where: { account, code, timestamp } })
105 | if (user) {
106 | return res.json(MESSAGE.USER_EXIST)
107 | } else {
108 | const user_code = Date.now().toString().substring(2)
109 | const userinfo = {
110 | account,
111 | password: md5(password),
112 | sex: 0,
113 | name: account,
114 | user_other_id: -1,
115 | code: user_code,
116 | status: 502,
117 | last_times: 3,
118 | total_times: 0,
119 | total_notes: 0,
120 | mode: 0,
121 | rate: 0,
122 | badge_id: -1,
123 | badges: '',
124 | ban_id: user_code + ',',
125 | face: 'https://airing.ursb.me/image/twolife/male.png'
126 | }
127 | await User.create(userinfo)
128 | return res.json({ ...MESSAGE.OK, data: userinfo })
129 | }
130 | }
131 | return res.json(MESSAGE.CODE_ERROR)
132 | }
133 |
134 | response()
135 | })
136 |
137 | router.post('/v3/confirm_sms', (req, res) => {
138 | const { account, code, timestamp, accountArea } = req.body
139 | validate(res, false, account, code, timestamp, accountArea)
140 |
141 | const findCode = async () => {
142 | return await Code.findOne({ where: { account: accountArea + account, code, timestamp } })
143 | }
144 |
145 | const response = async () => {
146 | const code = await findCode()
147 | if (code) {
148 | return res.json(MESSAGE.OK)
149 | } else {
150 | return res.json(MESSAGE.CODE_ERROR)
151 | }
152 | }
153 |
154 | response()
155 | })
156 |
157 | /* users/register */
158 | router.post('/v3/register', (req, res) => {
159 |
160 | // TODO: 重写逻辑
161 | const { account, password, code, timestamp, area } = req.body
162 | validate(res, false, account, password, code, timestamp, area)
163 |
164 | const findCode = async () => {
165 | return await Code.findOne({ where: { code, timestamp } })
166 | }
167 |
168 | const response = async () => {
169 | const code = await findCode()
170 | if (code) {
171 | const user = await User.findOne({ where: { account } })
172 | if (user) {
173 | return res.json(MESSAGE.USER_EXIST)
174 | } else {
175 | const user_code = Date.now().toString().substring(2)
176 | const userinfo = {
177 | account,
178 | password: md5(password),
179 | sex: 0,
180 | name: account,
181 | user_other_id: -1,
182 | code: user_code,
183 | status: 502,
184 | last_times: 3,
185 | total_times: 0,
186 | total_notes: 0,
187 | mode: 0,
188 | rate: 0,
189 | badge_id: -1,
190 | badges: '',
191 | ban_id: user_code + ',',
192 | face: '',
193 | area: '',
194 | vip: 0,
195 | date: Date.now()
196 | }
197 | await User.create(userinfo)
198 | return res.json({ ...MESSAGE.OK, data: userinfo })
199 | }
200 | }
201 | return res.json(MESSAGE.CODE_ERROR)
202 | }
203 |
204 | response()
205 | })
206 |
207 | /* users/v3/confirm_account */
208 | router.post('/v3/confirm_account', (req, res) => {
209 |
210 | const { account } = req.body
211 | validate(res, false, account)
212 |
213 | // TODO: 2.x 注册的要给优惠
214 | const response = async () => {
215 | const user = await User.findOne({ where: { account } })
216 | if (user) {
217 | return res.json(MESSAGE.USER_EXIST)
218 | } else {
219 | return res.json(MESSAGE.OK)
220 | }
221 | }
222 |
223 | response()
224 | })
225 |
226 |
227 | /* users/login */
228 | router.post('/login', (req, res) => {
229 |
230 | const { account, password } = req.body
231 | validate(res, false, account, password)
232 |
233 | const response = async () => {
234 | const user = await User.findOne({ where: { account }, include: [Badge] })
235 | if (!user) return res.json(MESSAGE.USER_NOT_EXIST)
236 |
237 | if (user.password !== md5(password))
238 | return res.json(MESSAGE.PASSWORD_ERROR)
239 |
240 | const timestamp = Date.now()
241 |
242 | const token = md5Pwd((user.id).toString() + timestamp.toString() + KEY)
243 |
244 | let partner = {}
245 | if (user.user_other_id !== -1) {
246 | partner = await User.findOne({ where: { id: user.user_other_id }, include: [Badge] })
247 | }
248 |
249 | return res.json({
250 | ...MESSAGE.OK,
251 | data: {
252 | user: { ...user.dataValues, password: 0 },
253 | key: { uid: user.id, token, timestamp },
254 | partner: { ...partner.dataValues, password: 0 }
255 | }
256 | })
257 | }
258 |
259 | response()
260 | })
261 |
262 | /* users/user */
263 | router.get('/user', (req, res) => {
264 |
265 | const { uid, timestamp, token, user_id } = req.query
266 | validate(res, true, uid, timestamp, token, user_id)
267 |
268 | const response = async () => {
269 | const user = await User.findOne({ where: { id: user_id }, include: [Badge] })
270 | if (!user) return res.json(MESSAGE.USER_NOT_EXIST)
271 | const partner = await User.findOne({ where: { id: user.user_other_id } })
272 | if (partner) {
273 | return res.json({
274 | ...MESSAGE.OK,
275 | data: { ...user.dataValues, password: 0 },
276 | partner: { ...partner.dataValues, password: 0 }
277 | })
278 | } else {
279 | return res.json({
280 | ...MESSAGE.OK,
281 | data: { ...user.dataValues, password: 0 },
282 | partner: {}
283 | })
284 | }
285 | }
286 |
287 | response()
288 | })
289 |
290 | /* users/update */
291 | router.post('/update', (req, res) => {
292 |
293 | const { uid, timestamp, token, sex, name, face, status, latitude = -1, longitude = -1, badge_id = -1, badges } = req.body
294 | validate(res, true, uid, timestamp, token, sex, name, face, status, latitude, longitude, badge_id)
295 |
296 | const response = async () => {
297 |
298 | await User.update({ name, sex, face, status, latitude, longitude, badge_id, badges }, { where: { id: uid } })
299 |
300 | const user = await User.findById(uid)
301 | if (!user) return res.json(MESSAGE.USER_NOT_EXIST)
302 |
303 | const partner = await User.findOne({ where: { id: user.user_other_id } })
304 |
305 | if (partner) {
306 | return res.json({
307 | ...MESSAGE.OK,
308 | data: {
309 | user: { ...user.dataValues, password: 0 },
310 | partner: { ...partner.dataValues, password: 0 }
311 | }
312 | })
313 | } else {
314 | return res.json({
315 | ...MESSAGE.OK,
316 | data: {
317 | user: { ...user.dataValues, password: 0 },
318 | partner: {}
319 | }
320 | })
321 | }
322 | }
323 |
324 | response()
325 | })
326 |
327 | /* users/disconnect */
328 | router.get('/disconnect', (req, res) => {
329 |
330 | const { uid, timestamp, token } = req.query
331 | validate(res, true, uid, timestamp, token)
332 |
333 | const response = async () => {
334 | const user = await User.findOne({ where: { id: uid } })
335 | const partner = await User.findOne({ where: { id: user.user_other_id } })
336 |
337 | JiGuangPush(user.user_other_id, '您被另一半解除匹配了:(,多写日记来记录自己的生活吧!')
338 | await Message.create({
339 | title: '您被另一半解除匹配了QAQ',
340 | type: 202,
341 | content: '',
342 | image: '',
343 | url: '',
344 | date: Date.now(),
345 | user_id: partner.id
346 | })
347 | await partner.increment('unread')
348 |
349 | let user_bans = user.ban_id + partner.code + ','
350 | let partner_bans = partner.ban_id + user.code + ','
351 |
352 | // 用户状态变为解除后的临界状态
353 | // 需要用户在匹配页面重新设置状态
354 | // 否则无法被匹配到
355 | // await User.update({ status: 0, user_other_id: -1, ban_id: user_bans }, { where: { id: user.user_other_id } })
356 | // await User.update({ status: 0, user_other_id: -1, ban_id: partner_bans }, { where: { id: uid } })
357 |
358 | // 2.0.5: 弱化匹配规则
359 | if (partner.sex === 1) {
360 | await User.update({ status: 113, user_other_id: -1, ban_id: user_bans }, { where: { id: user.user_other_id } })
361 | } else {
362 | await User.update({ status: 103, user_other_id: -1, ban_id: user_bans }, { where: { id: user.user_other_id } })
363 | }
364 |
365 | if (user.sex === 1) {
366 | await User.update({ status: 113, user_other_id: -1, ban_id: partner_bans }, { where: { id: uid } })
367 | } else {
368 | await User.update({ status: 103, user_other_id: -1, ban_id: partner_bans }, { where: { id: uid } })
369 | }
370 |
371 | // 清空双方的喜欢记录
372 | await Note.update({ is_liked: 0 }, { where: { user_id: [uid, user.user_other_id] } })
373 |
374 | return res.json(MESSAGE.OK)
375 | }
376 |
377 | response()
378 | })
379 |
380 | /* users/connect_by_random */
381 | router.get('/connect_by_random', (req, res) => {
382 |
383 | const { uid, timestamp, token } = req.query
384 | validate(res, true, uid, timestamp, token)
385 |
386 | const response = async () => {
387 |
388 | const user = await User.findOne({ where: { id: uid } })
389 |
390 | let condition = {}
391 |
392 | // status 的意义详见数据字典
393 | switch (user.status) {
394 | case 999:
395 | return res.json(MESSAGE.CONNECT_ERROR_CLOSE)
396 | break
397 | case 1000:
398 | return res.json(MESSAGE.CONNECT_ERROR_ALREADY)
399 | break
400 | case 101:
401 | condition = {
402 | status: { '$or': [111, 112, 113] },
403 | sex: 1,
404 | mode: user.mode > 50 ? { 'lte': 50 } : { 'gte': 50 },
405 | total_notes: { 'gte': 1 }
406 | }
407 | break
408 | case 102:
409 | condition = {
410 | status: { '$or': [111, 112, 113] },
411 | sex: 1,
412 | mode: user.mode > 50 ? { 'gte': 50 } : { 'lte': 50 },
413 | total_notes: { 'gte': 1 }
414 | }
415 | break
416 | case 103:
417 | condition = {
418 | status: { '$or': [111, 112, 113] },
419 | sex: 1,
420 | total_notes: { 'gte': 1 }
421 | }
422 | break
423 | case 111:
424 | condition = {
425 | status: { '$or': [101, 102, 103] },
426 | sex: 0,
427 | mode: user.mode > 50 ? { 'lte': 50 } : { 'gte': 50 },
428 | total_notes: { 'gte': 1 }
429 | }
430 | break
431 | case 112:
432 | condition = {
433 | status: { '$or': [101, 102, 103] },
434 | sex: 0,
435 | mode: user.mode > 50 ? { 'gte': 50 } : { 'lte': 50 },
436 | total_notes: { 'gte': 1 }
437 | }
438 | break
439 | case 113:
440 | condition = {
441 | status: { '$or': [101, 102, 103] },
442 | sex: 0,
443 | total_notes: { 'gte': 1 }
444 | }
445 | break
446 | case 201:
447 | condition = {
448 | status: { '$or': [201, 202, 203] },
449 | sex: 0,
450 | mode: user.mode > 50 ? { 'lte': 50 } : { 'gte': 50 },
451 | total_notes: { 'gte': 1 }
452 | }
453 | break
454 | case 202:
455 | condition = {
456 | status: { '$or': [201, 202, 203] },
457 | sex: 0,
458 | mode: user.mode > 50 ? { 'gte': 50 } : { 'lte': 50 },
459 | total_notes: { 'gte': 1 }
460 | }
461 | break
462 | case 203:
463 | condition = {
464 | status: { '$or': [211, 212, 213] },
465 | sex: 0,
466 | total_notes: { 'gte': 1 }
467 | }
468 | break
469 | case 211:
470 | condition = {
471 | status: { '$or': [211, 212, 213] },
472 | sex: 1,
473 | mode: user.mode > 50 ? { 'lte': 50 } : { 'gte': 50 },
474 | total_notes: { 'gte': 1 }
475 | }
476 | break
477 | case 212:
478 | condition = {
479 | status: { '$or': [211, 212, 213] },
480 | sex: 1,
481 | mode: user.mode > 50 ? { 'gte': 50 } : { 'lte': 50 },
482 | total_notes: { 'gte': 1 }
483 | }
484 | break
485 | case 213:
486 | condition = {
487 | status: { '$or': [201, 202, 203] },
488 | sex: 1,
489 | total_notes: { 'gte': 1 }
490 | }
491 | break
492 | case 501:
493 | return res.json(MESSAGE.CONNECT_ERROR_NO_TIME)
494 | break
495 | case 502:
496 | return res.json(MESSAGE.CONNECT_ERROR_NO_NOTE)
497 | break
498 | default:
499 | return res.json(MESSAGE.CONNECT_ERROR_BAN)
500 | break
501 | }
502 |
503 | const candidates = await User.findAll({
504 | where: {
505 | ...condition,
506 | code: { '$notIn': user.ban_id.split(',') }
507 | }
508 | })
509 |
510 | if (!candidates[0]) return res.json(MESSAGE.USER_NOT_EXIST)
511 |
512 | const partner = candidates[Math.floor(Math.random() * candidates.length)]
513 |
514 | if (user.last_times < 1) {
515 | // await User.update({ status: 501, user_other_id: partner.id, connect_at: Date.now() }, { where: { id: uid } })
516 | return res.json(MESSAGE.CONNECT_ERROR_NO_TIME)
517 | } else {
518 | await User.update({ status: 1000, user_other_id: partner.id, connect_at: Date.now() }, { where: { id: uid } })
519 | }
520 |
521 | await User.update({ status: 1000, user_other_id: uid, connect_at: Date.now() }, { where: { id: partner.id } })
522 |
523 | /**
524 | * 匹配逻辑
525 | * 1. 匹配次数为主动匹配的次数
526 | * 2. 匹配时暂只减少主动匹配者的次数
527 | * 3. 匹配次数为零只能被匹配
528 | */
529 |
530 | await user.decrement('last_times')
531 | await user.increment('total_times')
532 |
533 | // 通知对方被匹配
534 | JiGuangPush(partner.id, '您期待的另一半已经来了:),多写日记来记录自己的生活吧!')
535 | await Message.create({
536 | title: `${user.name} 成功匹配到了您,成为您的另一半`,
537 | type: 201,
538 | content: '',
539 | image: '',
540 | url: '',
541 | date: Date.now(),
542 | user_id: partner.id
543 | })
544 | await partner.increment('unread')
545 |
546 | return res.json({
547 | ...MESSAGE.OK,
548 | data: { ...partner.dataValues, password: 0 }
549 | })
550 | }
551 |
552 | response()
553 | })
554 |
555 | /* users/connect_by_id */
556 | router.get('/connect_by_id', (req, res) => {
557 |
558 | const { uid, timestamp, token, code } = req.query
559 | validate(res, true, uid, timestamp, token, code)
560 |
561 | const response = async () => {
562 | const user = await User.findOne({ where: { id: uid } })
563 | const partner = await User.findOne({ where: { code } })
564 |
565 | // 不允许匹配自己
566 | if (user.code === code) {
567 | return res.json(MESSAGE.USER_NOT_EXIST)
568 | }
569 | if (!partner) {
570 | return res.json(MESSAGE.USER_NOT_EXIST)
571 | }
572 |
573 | if (user.status === 501)
574 | return res.json(MESSAGE.CONNECT_ERROR_NO_TIME)
575 | if (user.status === 502 || partner.status === 502)
576 | return res.json(MESSAGE.CONNECT_ERROR_NO_NOTE)
577 | if (user.status === 503 || user.status === 504 || partner.status === 503 || partner.status === 504)
578 | return res.json(MESSAGE.CONNECT_ERROR_BAN)
579 | if (user.status === 999 || partner.status === 999)
580 | return res.json(MESSAGE.CONNECT_ERROR_CLOSE)
581 | if (user.user_other_id !== -1 || partner.user_other_id !== -1)
582 | return res.json(MESSAGE.CONNECT_ERROR_ALREADY)
583 |
584 | if (user.last_times < 1) {
585 | // await User.update({ status: 501, user_other_id: partner.id, connect_at: Date.now() }, { where: { id: uid } })
586 | return res.json(MESSAGE.CONNECT_ERROR_NO_NOTE)
587 | } else {
588 | await User.update({ status: 1000, user_other_id: partner.id, connect_at: Date.now() }, { where: { id: uid } })
589 | }
590 |
591 | await User.update({ status: 1000, user_other_id: uid, connect_at: Date.now() }, { where: { id: partner.id } })
592 |
593 | await user.decrement('last_times')
594 | await user.increment('total_times')
595 |
596 | // 通知对方被匹配
597 | JiGuangPush(partner.id, '您期待的另一半已经来了:),多写日记来记录自己的生活吧!')
598 | await Message.create({
599 | title: `${user.name} 成功匹配到了您,成为您的另一半`,
600 | type: 201,
601 | content: '',
602 | image: '',
603 | url: '',
604 | date: Date.now(),
605 | user_id: partner.id
606 | })
607 | await partner.increment('unread')
608 |
609 | return res.json({
610 | ...MESSAGE.OK,
611 | data: { ...partner.dataValues, password: 0 }
612 | })
613 | }
614 |
615 | response()
616 | })
617 |
618 | /* users/show_notification */
619 | router.get('/show_notification', (req, res) => {
620 |
621 | const { uid, timestamp, token } = req.query
622 | validate(res, true, uid, timestamp, token)
623 |
624 | const response = async () => {
625 | const data = await Message.findAll({ where: { user_id: [uid, -1] }, order: 'date DESC' })
626 | await User.update({ unread: 0 }, { where: { id: uid } })
627 | return res.json({ ...MESSAGE.OK, data })
628 | }
629 |
630 | response()
631 | })
632 |
633 | /* users/invitation_code */
634 | router.get('/invitation_code', (req, res) => {
635 |
636 | const { uid, timestamp, token, code } = req.query
637 | validate(res, true, uid, timestamp, token, code)
638 |
639 | const response = async () => {
640 | // TODO: 随机邀请码逻辑
641 | // const award = await Award.findOne({ where: { code, used: false } })
642 |
643 | if (code === 'airing5201314') {
644 | const user = await User.findOne({ where: { id: uid } })
645 | await User.update({ badges: user.badges + '1,', badge_id: 1 }, { where: { id: uid } })
646 | return res.json(MESSAGE.OK)
647 | } else {
648 | return res.json(MESSAGE.CODE_NOT_EXIST)
649 | }
650 | }
651 |
652 | response()
653 | })
654 |
655 |
656 | /* users/update_rate */
657 | router.post('/update_rate', (req, res) => {
658 |
659 | const { uid, timestamp, token, price } = req.body
660 | validate(res, true, uid, timestamp, token, price)
661 |
662 | const response = async () => {
663 | const user = await User.findOne({ where: { id: uid } })
664 | await user.increment('rate', { by: price })
665 | return res.json(MESSAGE.OK)
666 | }
667 |
668 | response()
669 | })
670 |
671 | /* users/add_last_times */
672 | router.post('/add_last_times', (req, res) => {
673 |
674 | const { uid, timestamp, token } = req.body
675 | validate(res, true, uid, timestamp, token)
676 |
677 | const response = async () => {
678 | const user = await User.findOne({ where: { id: uid } })
679 | await user.increment('rate')
680 | await user.increment('last_times')
681 | return res.json(MESSAGE.OK)
682 | }
683 |
684 | response()
685 | })
686 |
687 | /* users/close_connection */
688 | router.get('/close_connection', (req, res) => {
689 |
690 | const { uid, timestamp, token } = req.query
691 | validate(res, true, uid, timestamp, token)
692 |
693 | const response = async () => {
694 | await User.update({ status: 999 }, { where: { id: uid } })
695 | return res.json(MESSAGE.OK)
696 | }
697 |
698 | response()
699 | })
700 |
701 |
702 | /* users/oauth_login */
703 | router.post('/oauth_login', (req, res) => {
704 |
705 | const { code, type } = req.body
706 | validate(res, false, code, type)
707 |
708 | let options = {}
709 |
710 | if (type === 'app') {
711 | options = {
712 | uri: 'https://api.weixin.qq.com/sns/oauth2/access_token',
713 | qs: {
714 | appid: WX_APP_APPID,
715 | secret: WX_APP_APPSECRET,
716 | code,
717 | grant_type: 'authorization_code'
718 | },
719 | json: true
720 | }
721 | } else if (type === 'wxp') {
722 | options = {
723 | uri: 'https://api.weixin.qq.com/sns/jscode2session',
724 | qs: {
725 | appid: WXP_APPID,
726 | secret: WXP_SECRET,
727 | js_code: code,
728 | grant_type: 'authorization_code'
729 | },
730 | json: true
731 | }
732 | }
733 |
734 | const response = async () => {
735 |
736 | const data = await rp(options)
737 | const { openid } = data
738 |
739 | const user = await User.findOne({ where: { openid }, include: [Badge] })
740 |
741 | if (user) {
742 | const timestamp = Date.now()
743 | const token = md5Pwd((user.id).toString() + timestamp.toString() + KEY)
744 |
745 | let partner = {}
746 | if (user.user_other_id !== -1) {
747 | partner = await User.findOne({ where: { id: user.user_other_id }, include: [Badge] })
748 | }
749 |
750 | return res.json({
751 | ...MESSAGE.OK,
752 | data: {
753 | user: { ...user.dataValues, password: 0 },
754 | key: { uid: user.id, token, timestamp },
755 | partner: { ...partner.dataValues, password: 0 }
756 | }
757 | })
758 | } else {
759 | // 如果用户不存在,提示前端跳转绑定页面
760 | return res.json({
761 | ...MESSAGE.USER_NOT_EXIST,
762 | data: openid
763 | })
764 | }
765 | }
766 |
767 | response()
768 | })
769 |
770 | /* users/bind_account */
771 | router.post('/bind_account', (req, res) => {
772 | const { account, openid } = req.body
773 | validate(res, false, account, openid)
774 |
775 | const response = async () => {
776 | let user = await User.findOne({ where: { account } })
777 | if (user) {
778 | // 如果用户存在,就直接绑定
779 | await User.update({ openid }, { where: { account } })
780 | return res.json(MESSAGE.OK)
781 | } else {
782 | // 如果用户不存在,则先注册再绑定
783 | const user_code = Date.now().toString().substring(2)
784 | const userinfo = {
785 | account,
786 | password: md5(Date.now()),
787 | sex: 0,
788 | name: account,
789 | user_other_id: -1,
790 | code: user_code,
791 | status: 502,
792 | last_times: 3,
793 | total_times: 0,
794 | total_notes: 0,
795 | mode: 0,
796 | rate: 0,
797 | badge_id: -1,
798 | badges: '',
799 | ban_id: user_code + ',',
800 | openid,
801 | face: 'https://airing.ursb.me/image/twolife/male.png'
802 | }
803 | await User.create(userinfo)
804 | // 提示前端需要完成后续步骤:补充性别与昵称
805 | user = await User.findOne({ where: { openid } })
806 | const timestamp = Date.now()
807 | const token = md5Pwd((user.id).toString() + timestamp.toString() + KEY)
808 | return res.json({
809 | ...MESSAGE.USER_NOT_EXIST,
810 | data: userinfo,
811 | key: { uid: user.id, token, timestamp },
812 | })
813 | }
814 | }
815 |
816 | response()
817 | })
818 |
819 | /* users/wxp_login */
820 | router.post('/wxp_login', (req, res) => {
821 |
822 | // userInfo 可以为空,因为存在用户不同意授权的情况
823 | // 登录凭证 code 获取 session_key 和 openid
824 | const { code, userInfo } = req.body
825 | validate(res, false, code)
826 |
827 | let options = {
828 | uri: 'https://api.weixin.qq.com/sns/jscode2session',
829 | qs: {
830 | appid: WXP_APPID,
831 | secret: WXP_SECRET,
832 | js_code: code,
833 | grant_type: 'authorization_code'
834 | },
835 | json: true
836 | }
837 |
838 | const response = async () => {
839 |
840 | const data = await rp(options)
841 | const { openid } = data
842 |
843 | if (!openid) {
844 | return res.json(MESSAGE.REQUEST_ERROR)
845 | }
846 |
847 | let user = await User.findOne({ where: { openid }, include: [Badge] })
848 |
849 | if (!user) {
850 | // 如果用户不存在,若是初次登录就替用户注册
851 |
852 | const info = userInfo
853 | const user_code = Date.now().toString().substring(2)
854 |
855 | await User.create({
856 | account: openid,
857 | password: md5(Date.now()),
858 | sex: info.gender === 1 ? 0 : 1, // 微信登录 0未填写 1男 2女
859 | name: info.nickName,
860 | user_other_id: -1,
861 | code: user_code,
862 | status: 502,
863 | last_times: 3,
864 | total_times: 0,
865 | total_notes: 0,
866 | mode: 0,
867 | rate: 0,
868 | badge_id: -1,
869 | badges: '',
870 | ban_id: user_code + ',',
871 | openid,
872 | face: info.avatarUrl
873 | })
874 |
875 | user = await User.findOne({ where: { openid }, include: [Badge] })
876 | }
877 |
878 | const timestamp = Date.now()
879 | const token = md5Pwd((user.id).toString() + timestamp.toString() + KEY)
880 |
881 | let partner = {}
882 | if (user.user_other_id !== -1) {
883 | partner = await User.findOne({ where: { id: user.user_other_id }, include: [Badge] })
884 | }
885 | partner.password = 0
886 |
887 | return res.json({
888 | ...MESSAGE.OK,
889 | data: {
890 | user: { ...user.dataValues, password: 0 },
891 | key: { uid: user.id, token, timestamp },
892 | partner
893 | },
894 | is_checking: IS_CHECKING
895 | })
896 | }
897 |
898 | response()
899 | })
900 |
901 | /* users/feedback */
902 | router.post('/feedback', (req, res) => {
903 |
904 | const { uid, token, timestamp, title, content, type, brand = '', systemVersion = '' } = req.body
905 | validate(res, false, uid, token, timestamp, title, content, type)
906 |
907 | const { version = '' } = req.query
908 |
909 | let labels = ['discussion']
910 | let feedbackType = ''
911 | let platform = 'ios'
912 |
913 | switch (type) {
914 | case 101:
915 | labels = ['ios', 'bug']
916 | feedbackType = '缺陷'
917 | platform = 'iOS'
918 | break
919 | case 102:
920 | labels = ['android', 'bug']
921 | feedbackType = '缺陷'
922 | platform = 'Android'
923 | break
924 | case 103:
925 | labels = ['微信小程序', 'bug']
926 | feedbackType = '缺陷'
927 | platform = '微信小程序'
928 | break
929 | case 200:
930 | labels = ['feature request']
931 | feedbackType = '需求'
932 | break
933 | case 300:
934 | feedbackType = '用户'
935 | break
936 | default:
937 | break
938 | }
939 |
940 | const response = async () => {
941 |
942 | const user = await User.findById(uid)
943 |
944 | const body = `\n ${user.name}\n Brand:${brand}, System:${systemVersion}, Version:${version}\n --- \n${content}`
945 |
946 | let options = {
947 | uri: 'https://api.github.com/repos/oh-bear/2life/issues',
948 | method: 'POST',
949 | body: {
950 | title,
951 | body,
952 | labels
953 | },
954 | headers: {
955 | 'Authorization': 'token ' + GITHUB_TOKEN,
956 | 'User-Agent': '2life-APP'
957 | },
958 | json: true
959 | }
960 |
961 | await Feedback.create({
962 | title,
963 | content,
964 | type,
965 | user_id: uid,
966 | brand,
967 | system_version: systemVersion,
968 | version
969 | })
970 | await rp(options) // 此处请求时间太长,前端可以不必等待响应
971 |
972 | let webhookOptions = {
973 | uri: `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${WEBHOOK_KEY_WECHAT}`,
974 | method: 'POST',
975 | body: {
976 | msgtype: 'markdown',
977 | markdown: {
978 | content: `实时新增 1 例${feedbackType}反馈,请相关成员注意。
979 | > 用户 id: ${uid}
980 | > 平台: ${platform}
981 | > 手机型号: ${brand}
982 | > 系统版本: ${systemVersion}
983 | > 软件版本: ${version}
984 |
985 | 反馈内容 @${user.name}:
986 | ${content}
987 | `,
988 | }
989 | },
990 | json: true
991 | }
992 | await rp(webhookOptions)
993 |
994 | return res.json(MESSAGE.OK)
995 | }
996 |
997 | response()
998 | })
999 |
1000 | /* users/delete_notification */
1001 | router.get('/delete_notification', (req, res) => {
1002 |
1003 | const { uid, token, timestamp, message_id } = req.query
1004 | validate(res, false, uid, token, timestamp, message_id)
1005 |
1006 | const response = async () => {
1007 | await Message.destroy({ where: { id: message_id } })
1008 | return res.json(MESSAGE.OK)
1009 | }
1010 |
1011 | response()
1012 | })
1013 |
1014 | /* users/check_token */
1015 | router.get('/check_token', (req, res) => {
1016 |
1017 | const { uid, token, timestamp } = req.query
1018 |
1019 | const response = async () => {
1020 | if (token !== md5Pwd(uid.toString() + timestamp.toString() + KEY)) {
1021 | return res.json(MESSAGE.TOKEN_ERROR)
1022 | }
1023 | return res.json(MESSAGE.OK)
1024 | }
1025 |
1026 | response()
1027 | })
1028 |
1029 | /* users/check_uid */
1030 | router.get('/check_uid', (req, res) => {
1031 |
1032 | const { uid } = req.query
1033 |
1034 | const response = async () => {
1035 | const user = await User.findOne({ where: { id: uid } })
1036 | if (!user) {
1037 | return res.json(MESSAGE.USER_NOT_EXIST)
1038 | }
1039 | return res.json(MESSAGE.OK)
1040 | }
1041 |
1042 | response()
1043 | })
1044 |
1045 |
1046 | /* 通过量表计算用户性格基础
1047 | * content = '2,1,2,1,1,1,2,2,2,1,1,1,2,2,2,'
1048 | */
1049 | router.post('/calculate_emotion', (req, res) => {
1050 | const { uid, timestamp, token, content } = req.body
1051 | validate(res, true, uid, timestamp, token)
1052 |
1053 | const response = async () => {
1054 | const answerE = (content.substring(0, 5)).split(',')
1055 | const answerC = (content.substring(6, 11)).split(',')
1056 | const answerO = (content.substring(12, 17)).split(',')
1057 | const answerA = (content.substring(18, 23)).split(',')
1058 | const answerN = (content.substring(24, 28)).split(',')
1059 |
1060 | const e = ((answerE[0] * 0.751 + answerE[1] * 0.686 + answerE[2] * 0.673) / 3)
1061 | const c = ((answerC[0] * 0.571 + answerC[1] * 0.707 + answerC[2] * 0.674) / 3)
1062 | const o = ((answerO[0] * 0.619 + answerO[1] * 0.704 + answerO[2] * 0.641) / 3)
1063 | const a = ((answerA[0] * 0.588 + answerA[1] * 0.602 + answerA[2] * 0.633) / 3)
1064 | const n = ((answerN[0] * 0.628 + answerN[1] * 0.708 + answerN[2] * 0.713) / 3)
1065 |
1066 | const emotions_basis = e + ',' + c + ',' + o + ',' + a + ',' + n
1067 |
1068 | let max = Math.max(e, c, o, a, n)
1069 |
1070 | let emotions_types, emotions_type, emotions_report
1071 | let tag = ''
1072 |
1073 | switch (max) {
1074 | case a:
1075 | tag = 'n'
1076 | emotions_types = ['实干主义者', '心灵多面手', '温和思想家', '自我笃行者']
1077 | emotions_report = `为人谦逊温和,不爱与他人争论。在有意无意中可能会降低自己的原则或者标准,和温柔不同,温和是性格,温柔是态度。你是个温和的人,不爱计较,喜欢忍让,在忍让的过程中,可能会积攒起负能量灾区。一旦导火索被引燃,就容易陷入情绪爆炸。(情绪解读)
1078 | 你在学业和事业方面是不温不火的,有自己的节奏,或快或慢,但都在自己的掌控当中,不爱跟他人作比较,觉得自己的事情不需要跟他人进行对比。你有一个属于自己的小宇宙。常常沉浸在自我的小世界中。你擅长进行独自深入地思考,常常会有异于常人的灵感迸发。温和的你可以适当的调整自己的步伐,跟随自己的心。心所向,意所达。(学业事业)
1079 | 温和平静的性格可能帮助你在状态上达到平衡,健康的状态能维持很长时间。但在遇到突发事件时,还可以多增进自己的应激能力。同时可以去尝试新的事物,增长自己的见识和开拓眼界。做一个温文尔雅,内涵饱满的儒雅之士。(健康身心)`
1080 | break
1081 | case e:
1082 | tag = 'e'
1083 | emotions_types = ['恬淡小天使', '温暖小甜心', '元气小青年', '品质小资']
1084 | emotions_report = `你在工作或学习上尽心尽责、勤奋可靠,你认真、合理地安排自己的精力和时间,以便工作时的每一分钟都能够起到最大的作用,从而提高工作的效率和质量,你容易和同事同学建立良好的关系,获得更多的帮助和支持,但有时候过度地要求自己可能会让你陷入病态的完美主义和强迫行为的困境,“要么不做,要做就要做到最好”并不是一句好的座右铭。尝试着告诉自己——我们必须从整体上接纳和遵从我们生命的限制,然后寻找最佳的或是接近最佳的方式来分配我们的时间和精力。(学业)
1085 | 你容易获得广泛的社会支持力量,一个丰富的、有支持感的社交生活可以有效降低心血管疾病、癌症的风险,且能使人有更长的寿命。归属感和社会联结可降低心理疾病的风险,增加生活意义感。与医生保持良好的沟通,较少出现酗酒或物质滥用等问题。(健康)
1086 | 你对待学业和工作认真尽责的态度会对伴侣起到一个良好的促进作用,帮助TA也在自己的领域内获得成就。同时,细心体贴的你更容易悉心照料到伴侣的种种情绪,是个十足的小棉袄,乐于付出的你不妨常和伴侣交流对感情的期望更容易让彼此获得令双方都满意的亲密关系。(爱情)`
1087 | break
1088 | case c:
1089 | tag = 'c'
1090 | emotions_types = ['躁动小魔王', '科学小怪人', '极致主义者', '暴躁领袖']
1091 | emotions_report = `对生活抱有极大热忱的你,有时候难免会过度关注生活中负面的信息,尤其是与自身相关的方面,所以总是在一个又一个难以入眠的夜晚细细数着白天是否完成自己的计划、离自己的目标有没有更进一步……总是觉得自己没有达到理想中的自己。但正是反复的思考和担忧让你对目标和方向更加清晰明确,也提前对即将到来的困难做好准备。对风险和困难的敏感是你永不停歇的奋斗源泉。(学业)
1092 | 尽管容易受到负面信息的影响,造成情绪波动,从而进行间歇性的暴饮暴食和抽烟喝酒,若是长时间陷入焦虑但是通常你对自己的健康状况非常警觉,身体上一点小小的问题也会让你警惕起来,去医院寻求治疗,所以重症疾病很容易被你扼杀在萌芽里。天性外向开朗的你更容易在遇到困境或是心情低落时寻求朋友的帮助。(健康)
1093 | 虽然有时候神经敏感会让你过度解读伴侣的一言一行,例如TA的面无表情会让你认为是一种冷漠无奈的抵抗。但是你会更加容易和伴侣建立起沟通机制。在沟通这件事上,我们总是误以为自己的非言语线索足够让对方明白我们想表达的意思,但其实,不论在一起多久、多么有默契的伴侣也通常难以做到这一点,这时候需要我们冷静下来,把思绪仔细地告诉对方。(爱情)`
1094 | break
1095 | case o:
1096 | tag = 'o'
1097 | emotions_types = ['厌世大魔王', '灵性创作家', '小世界掌控家', '灵魂多面手']
1098 | emotions_report = `大到漫漫一生,小到一天的安排,你总是对此小心翼翼提心吊胆,似乎失去一丁点的掌控都足以让你窒息抓狂。你很容易受到外界的影响而产生较大的情绪波动,对负面信息比较在意,你经常反复思考和担忧。但也正是这思考,让你比常人更多一份创造力。(情绪解读)
1099 | 你在学业和事业方面一定是一个相当有创造力的人,你擅长从细节处不断进行深入地思考,从而能够触类旁通不断进行发散,在现有结论的基础上进行再创造。但是有时候不必在细节处过于纠结,而是学会放眼全局,说不定能收获更加开阔的视野。(学业事业)
1100 | 更加开放的性格可能帮助你在心理上保持健康,心理上的健康不仅指更加积极乐观、对压力的处理能力更强,而且更加容易让你保持健康的饮食和运动习惯。同时你愿意去尝试新的事物,寻求新异和多样性、尝试新的活动更加有利于你在经历创伤事件后的恢复,保持平和开放的心态。(健康身心)`
1101 | break
1102 | case n:
1103 | tag = 'n'
1104 | emotions_types = ['忧郁小王子', '忧伤小绵羊', '谦和小智者', '忧郁小麋鹿']
1105 | emotions_report = `为人低调谦和,虽然不常生气,但是也没有能很好地控制自己的情绪。你经常需要治愈系的聆听者,希望将自己的心事告诉挚友。你身上总是有温暖的光亮。美好的事物各不相同,世界也瞬息万变,而你是一颗永恒的星星,因为自身会发出温暖而明亮的光芒,所以你不惧怕黑暗,你是如此美好,让恐惧烟消云散,你是这个星球上的一点希望。(情绪解读)
1106 | 你在学业和事业方面一定是一个相当有潜力的人,不论是脑海里还是胸腔中,都藏着一个大大的宇宙。你擅长从细节处不断进行深入地思考,从而能够触类旁通不断进行发散,在现有结论的基础上进行再创造。但是有时候不必在细节处过于纠结,而是学会放眼全局,说不定能收获更加开阔的视野。(学业事业)
1107 | 更加开放的性格可能帮助你在心理上保持健康,心理上的健康不仅指更加积极乐观、对压力的处理能力更强,而且更加容易让你保持健康的饮食和运动习惯。同时你愿意去尝试新的事物,寻求新异和多样性、尝试新的活动更加有利于你在经历创伤事件后的恢复,保持平和开放的心态。(健康身心)`
1108 | break
1109 | default:
1110 | break
1111 | }
1112 |
1113 | let type_id = Math.floor(Math.random() * 4)
1114 | emotions_type = emotions_types[type_id]
1115 | let emotions_url = tag + type_id
1116 |
1117 | // 用户在测试之前写的日记不会记录性格计算
1118 | await User.update({
1119 | emotions_basis,
1120 | emotions: emotions_basis,
1121 | emotions_type,
1122 | emotions_report
1123 | }, { where: { id: uid } })
1124 |
1125 | return res.json({
1126 | ...MESSAGE.OK,
1127 | data: { emotions_basis, emotions: emotions_basis, emotions_type, emotions_report, emotions_url }
1128 | })
1129 | }
1130 |
1131 | response()
1132 | })
1133 |
1134 | /* /users/update_vip */
1135 | router.get('/update_vip', (req, res) => {
1136 |
1137 | const { uid, timestamp, token, expires } = req.query
1138 | validate(res, true, uid, timestamp, token, expires)
1139 |
1140 | const response = async () => {
1141 | await User.update({
1142 | vip: 1,
1143 | vip_expires: expires
1144 | }, { where: { id: uid } })
1145 |
1146 | return res.json(MESSAGE.OK)
1147 | }
1148 |
1149 | response()
1150 | })
1151 |
1152 | /* /users/enroll_activity */
1153 | router.get('/enroll_activity', (req, res) => {
1154 |
1155 | const { uid, timestamp, token, type, code } = req.query
1156 | validate(res, true, uid, timestamp, token, type)
1157 |
1158 | const response = async () => {
1159 | // 活动报名时间
1160 | if (Date.now() > new Date('2018-08-20 0:0:0').getTime())
1161 | return res.json(MESSAGE.REQUEST_ERROR)
1162 |
1163 | if (await Activity.findOne({ where: { user_id: uid } }))
1164 | return res.json({ code: 666, message: '用户已报名' })
1165 |
1166 | const user = await User.findOne({ where: { id: uid } })
1167 | const act = {
1168 | 'process': 0,
1169 | 'gold': 7,
1170 | 'finished': 0,
1171 | 'beginline': new Date('2018-08-20 0:0:0').getTime(),
1172 | 'deadline': new Date('2018-08-27 0:0:0').getTime(),
1173 | }
1174 |
1175 | // type: 0 未匹配用户随机匹配, 1 已匹配用户直接报名, 2 邀请匹配
1176 | if (parseInt(type) === 0) {
1177 | if (user.user_other_id !== -1)
1178 | return res.json(MESSAGE.CONNECT_ERROR_ALREADY)
1179 |
1180 | await Activity.create(Object.assign(act, {
1181 | 'user_id': uid,
1182 | 'user_other_id': -1,
1183 | 'state': user.status,
1184 | 'success': 0,
1185 | }))
1186 |
1187 | JiGuangPush(uid, '你已经成功报名了七夕节活动!敬请期待你的另一半吧!')
1188 |
1189 | await Message.create({
1190 | title: '你已经成功报名了七夕节活动!敬请期待你的另一半吧!',
1191 | type: 201,
1192 | content: '',
1193 | image: '',
1194 | url: '',
1195 | date: Date.now(),
1196 | user_id: uid
1197 | })
1198 | await user.increment('unread')
1199 | }
1200 |
1201 | if (parseInt(type) === 1) {
1202 | if (user.user_other_id === -1)
1203 | return res.json(MESSAGE.REQUEST_ERROR)
1204 |
1205 | let partner = await User.findOne({ where: { id: user.user_other_id } })
1206 |
1207 | await Activity.create(Object.assign(act, {
1208 | 'user_id': uid,
1209 | 'user_other_id': user.user_other_id,
1210 | 'state': user.status,
1211 | 'success': 1,
1212 | }))
1213 |
1214 | await Activity.create(Object.assign(act, {
1215 | 'user_id': user.user_other_id,
1216 | 'user_other_id': uid,
1217 | 'state': user.status,
1218 | 'success': 1,
1219 | }))
1220 | JiGuangPush(uid, `你和${partner.name}成功报名了七夕节活动!快去完成活动赢取奖励吧!`)
1221 | JiGuangPush(partner.id, `你和${user.name}成功报名了七夕节活动!快去完成活动赢取奖励吧!`)
1222 |
1223 | await Message.create({
1224 | title: `你和${partner.name}成功报名了七夕节活动!快去完成活动赢取奖励吧!`,
1225 | type: 201,
1226 | content: '',
1227 | image: '',
1228 | url: '',
1229 | date: Date.now(),
1230 | user_id: uid
1231 | })
1232 | await user.increment('unread')
1233 |
1234 | await Message.create({
1235 | title: `你和${user.name}成功报名了七夕节活动!快去完成活动赢取奖励吧!`,
1236 | type: 201,
1237 | content: '',
1238 | image: '',
1239 | url: '',
1240 | date: Date.now(),
1241 | user_id: partner.id
1242 | })
1243 | await partner.increment('unread')
1244 |
1245 | return res.json({
1246 | user: { ...user.dataValues, password: 0 },
1247 | partner: { ...partner.dataValues, password: 0 },
1248 | ...MESSAGE.OK
1249 | })
1250 | }
1251 |
1252 | if (parseInt(type) === 2) {
1253 | if (!code)
1254 | return res.json(MESSAGE.PARAMETER_ERROR)
1255 |
1256 | const user_other = await User.findOne({ where: { code } })
1257 |
1258 | if (!user_other)
1259 | return res.json(MESSAGE.USER_NOT_EXIST)
1260 |
1261 | if (user_other.user_other_id !== -1 || user.user_other_id !== -1)
1262 | return res.json(MESSAGE.CONNECT_ERROR_ALREADY)
1263 |
1264 | // 为2人进行匹配
1265 | await User.update({ status: 1000, user_other_id: user_other.id, connect_at: Date.now() }, { where: { id: uid } })
1266 | await User.update({ status: 1000, user_other_id: uid, connect_at: Date.now() }, { where: { id: user_other.id } })
1267 |
1268 | // 通知对方被匹配
1269 | JiGuangPush(uid, `你和${user_other.name}的配对成功了!努力完成活动赢取奖励吧!`)
1270 | JiGuangPush(user_other.id, `你和${user.name}配对成功了!努力完成活动赢取奖励吧!`)
1271 |
1272 | await Message.create({
1273 | title: `你和${user_other.name}的配对成功了!努力完成活动赢取奖励吧!`,
1274 | type: 201,
1275 | content: '',
1276 | image: '',
1277 | url: '',
1278 | date: Date.now(),
1279 | user_id: uid
1280 | })
1281 | await user.increment('unread')
1282 |
1283 | await Message.create({
1284 | title: `你和${user.name}配对成功了!努力完成活动赢取奖励吧!`,
1285 | type: 201,
1286 | content: '',
1287 | image: '',
1288 | url: '',
1289 | date: Date.now(),
1290 | user_id: user_other.id
1291 | })
1292 | await user_other.increment('unread')
1293 |
1294 | // 为2人报名活动
1295 | await Activity.create(Object.assign(act, {
1296 | 'user_id': uid,
1297 | 'user_other_id': user_other.id,
1298 | 'state': user.status,
1299 | 'success': 1,
1300 | }))
1301 |
1302 | await Activity.create(Object.assign(act, {
1303 | 'user_id': user_other.id,
1304 | 'user_other_id': uid,
1305 | 'state': user_other.status,
1306 | 'success': 1,
1307 | }))
1308 |
1309 | let partner = await User.findOne({ where: { id: user_other.id } })
1310 |
1311 | return res.json({
1312 | user: { ...user.dataValues, password: 0 },
1313 | partner: { ...partner.dataValues, password: 0 },
1314 | ...MESSAGE.OK
1315 | })
1316 | }
1317 |
1318 | return res.json(MESSAGE.OK)
1319 | }
1320 |
1321 | response()
1322 | })
1323 |
1324 | /* /users/update_activity */
1325 | router.get('/update_activity', (req, res) => {
1326 |
1327 | const { uid, timestamp, token } = req.query
1328 | validate(res, true, uid, timestamp, token)
1329 |
1330 | const response = async () => {
1331 | // 活动进行时间
1332 | if ((new Date('2018-08-20 0:0:0').getTime() > Date.now()) || (Date.now() > new Date('2018-08-27 0:0:0').getTime()))
1333 | return res.json(MESSAGE.REQUEST_ERROR)
1334 |
1335 | const act = await Activity.findOne({ where: { user_id: uid } })
1336 |
1337 | if (!act)
1338 | return res.json(MESSAGE.REQUEST_ERROR)
1339 |
1340 | const user = await User.findOne({ where: { id: uid } })
1341 |
1342 | // 中途解除匹配,
1343 | // 一天时间给后台修改随机匹配的user.user_other_id和activity的success
1344 | if ((user.user_other_id !== act.user_other_id) && (new Date('2018-08-21').getTime() < Date.now())) {
1345 | await Activity.update({ 'finished': -1 }, { where: { user_id: uid } })
1346 | return res.json(MESSAGE.REQUEST_ERROR)
1347 | }
1348 |
1349 | // 没有连续写日记
1350 | if ((new Date().getDate() - 20) > act.process) {
1351 | await Activity.update({ 'finished': -2 }, { where: { user_id: uid } })
1352 | return res.json(MESSAGE.REQUEST_ERROR)
1353 | }
1354 |
1355 | if ((act.process + 1) === act.gold) {
1356 | await Activity.update({
1357 | 'process': act.process + 1,
1358 | 'finished': 1
1359 | }, { where: { user_id: uid } })
1360 | } else {
1361 | await Activity.update({ 'process': new Date().getDate() - 19 }, { where: { user_id: uid } })
1362 | }
1363 |
1364 | return res.json(MESSAGE.OK)
1365 | }
1366 |
1367 | response()
1368 | })
1369 |
1370 | /* /users/get_activity */
1371 | router.get('/get_activity', (req, res) => {
1372 |
1373 | const { uid, timestamp, token } = req.query
1374 | validate(res, true, uid, timestamp, token)
1375 |
1376 | const response = async () => {
1377 | if ((new Date('2018-08-15').getTime() > Date.now()) || (Date.now() > new Date('2018-08-27').getTime()))
1378 | return res.json(MESSAGE.REQUEST_ERROR)
1379 |
1380 | const act = await Activity.findOne({ where: { user_id: uid } })
1381 |
1382 | if (!act)
1383 | return res.json(MESSAGE.REQUEST_ERROR)
1384 |
1385 | let partner = { dataValues: {} }
1386 | if (act.user_other_id !== -1) {
1387 | partner = await User.findOne({ where: { id: act.user_other_id } })
1388 | }
1389 |
1390 | const user = await User.findOne({ where: { id: uid } })
1391 |
1392 | return res.json({
1393 | user: { ...user.dataValues, password: 0 },
1394 | partner: { ...partner.dataValues, password: 0 },
1395 | act,
1396 | ...MESSAGE.OK
1397 | })
1398 | }
1399 |
1400 | response()
1401 | })
1402 |
1403 | router.post('/reset_password', (req, res) => {
1404 |
1405 | const { account, password, code, timestamp } = req.body
1406 | validate(res, false, account, password, code, timestamp)
1407 |
1408 | const findCode = async () => {
1409 | return await Code.findOne({ where: { code, timestamp } })
1410 | }
1411 |
1412 | const response = async () => {
1413 | const code = await findCode()
1414 | if (code) {
1415 | const user = await User.findOne({ where: { account } })
1416 | if (!user) {
1417 | return res.json(MESSAGE.USER_NOT_EXIST)
1418 | } else {
1419 | await User.update({ password: md5(password) }, { where: { account } })
1420 | return res.json(MESSAGE.OK)
1421 | }
1422 | }
1423 | return res.json(MESSAGE.CODE_ERROR)
1424 | }
1425 |
1426 | response()
1427 | })
1428 |
1429 | module.exports = router
1430 |
--------------------------------------------------------------------------------