init
This commit is contained in:
commit
b978b16e6a
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/.env
|
||||||
|
/.idea/
|
||||||
|
|
||||||
|
**/node_modules
|
||||||
|
**/.DS_Store
|
||||||
|
|
||||||
|
/config/config.js
|
||||||
|
/config/game_dic.js
|
||||||
|
|
||||||
|
/public
|
||||||
|
/logs
|
||||||
|
/build
|
||||||
|
/dist
|
||||||
|
/lib
|
||||||
|
rev-manifest.json
|
||||||
|
/yarn.lock
|
||||||
|
/nohup.out
|
||||||
|
/package-lock.json
|
||||||
|
/dist.tar.gz
|
||||||
|
*.swp
|
||||||
|
/logsgarfield
|
||||||
|
.vscode/launch.json
|
42
package.json
Normal file
42
package.json
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "admin-be",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"dev": "cross-env nodemon src/app.js --exec babel-node "
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"bluebird": "^3.5.4",
|
||||||
|
"body-parser": "^1.19.0",
|
||||||
|
"bunyan": "^1.8.12",
|
||||||
|
"compression": "^1.7.4",
|
||||||
|
"connect-mongo": "^2.0.3",
|
||||||
|
"cookie-parser": "^1.4.4",
|
||||||
|
"express": "^4.16.4",
|
||||||
|
"express-flash": "0.0.2",
|
||||||
|
"express-session": "^1.16.1",
|
||||||
|
"express-validator": "^5.3.1",
|
||||||
|
"file-stream-rotator": "^0.4.1",
|
||||||
|
"fs-extra": "^8.0.0",
|
||||||
|
"glob": "^7.1.4",
|
||||||
|
"helmet": "^3.18.0",
|
||||||
|
"ldapjs": "^1.0.2",
|
||||||
|
"method-override": "^3.0.0",
|
||||||
|
"mongoose": "^5.5.7",
|
||||||
|
"morgan": "^1.9.1",
|
||||||
|
"node-schedule": "^1.3.2",
|
||||||
|
"nodemon": "^1.19.0",
|
||||||
|
"request": "^2.88.0",
|
||||||
|
"serve-favicon": "^2.5.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-cli": "^6.26.0",
|
||||||
|
"babel-preset-env": "^1.7.0",
|
||||||
|
"cross-env": "^5.2.0"
|
||||||
|
}
|
||||||
|
}
|
35
src/app.js
Normal file
35
src/app.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
'use strict';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import config from '../config/config';
|
||||||
|
import app from './bin/express';
|
||||||
|
import glob from 'glob';
|
||||||
|
import Promise from 'bluebird';
|
||||||
|
import logger from './utils/logger';
|
||||||
|
import http from 'http';
|
||||||
|
import msgSchedule from './schedule/weappmsg.schedule';
|
||||||
|
|
||||||
|
mongoose.Promise = Promise;
|
||||||
|
|
||||||
|
const db = mongoose.connection;
|
||||||
|
|
||||||
|
db.on('error', function(err) {
|
||||||
|
logger.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
db.once('open', function() {
|
||||||
|
logger.info('Connected to db.');
|
||||||
|
});
|
||||||
|
mongoose.connect(config.db_admin, {promiseLibrary: Promise, useNewUrlParser: true});
|
||||||
|
const models = glob.sync(config.root + './src/models/*.js');
|
||||||
|
models.forEach(function(model) {
|
||||||
|
require(model);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const server = http.createServer(app);
|
||||||
|
|
||||||
|
msgSchedule.scheduleSendAll();
|
||||||
|
|
||||||
|
server.listen(config.port, function() {
|
||||||
|
logger.info(`${config.app.name} garfield server listening on port ${config.port}`);
|
||||||
|
});
|
183
src/bin/express.js
Normal file
183
src/bin/express.js
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import express from 'express';
|
||||||
|
import expressValidator from 'express-validator';
|
||||||
|
import flash from 'express-flash';
|
||||||
|
import session from 'express-session';
|
||||||
|
import bodyParser from 'body-parser';
|
||||||
|
import cookieParser from 'cookie-parser';
|
||||||
|
import compress from 'compression';
|
||||||
|
import methodOverride from 'method-override';
|
||||||
|
import helmet from 'helmet';
|
||||||
|
// import favicon from 'serve-favicon';
|
||||||
|
import FileStreamRotator from 'file-stream-rotator';
|
||||||
|
import morgan from 'morgan';
|
||||||
|
import fs from 'fs';
|
||||||
|
import logger from './../utils/logger'
|
||||||
|
import expressUtils from './../utils/express-utils';
|
||||||
|
import config from './../../config/config';
|
||||||
|
import connectMongo from 'connect-mongo';
|
||||||
|
import routes from './../router/index';
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const MongoStore = connectMongo(session);
|
||||||
|
const env = process.env.NODE_ENV || 'development';
|
||||||
|
const isDev = env === 'development';
|
||||||
|
app.locals.ENV = env;
|
||||||
|
app.locals.ENV_DEVELOPMENT = isDev;
|
||||||
|
|
||||||
|
app.use(helmet());
|
||||||
|
|
||||||
|
app.disable('x-powered-by');
|
||||||
|
// app.use(favicon(config.root + '/public/img/favicon/favicon.ico'));
|
||||||
|
|
||||||
|
const logDir = config.logs_path;
|
||||||
|
fs.existsSync(logDir) || fs.mkdirSync(logDir);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录除favicon之外的所有请求
|
||||||
|
*/
|
||||||
|
const accessLogStream = FileStreamRotator.getStream({
|
||||||
|
date_format: 'YYYYMMDD',
|
||||||
|
filename: logDir + '/' + config.app.name + '-access-%DATE%.log',
|
||||||
|
frequency: 'daily',
|
||||||
|
verbose: false
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* 仅记录失败的请求
|
||||||
|
*/
|
||||||
|
const errorLogStream = FileStreamRotator.getStream({
|
||||||
|
date_format: 'YYYYMMDD',
|
||||||
|
filename: logDir + '/' + config.app.name + '-error-%DATE%.log',
|
||||||
|
frequency: 'daily',
|
||||||
|
verbose: false
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* 记录非静态文件请求
|
||||||
|
*/
|
||||||
|
const requestLogStream = FileStreamRotator.getStream({
|
||||||
|
date_format: 'YYYYMMDD',
|
||||||
|
filename: logDir + '/' + config.app.name + '-request-%DATE%.log',
|
||||||
|
frequency: 'daily',
|
||||||
|
verbose: false
|
||||||
|
});
|
||||||
|
|
||||||
|
morgan.token('remote-addr', function(req, res) {
|
||||||
|
const ip =
|
||||||
|
req.headers['x-real-ip'] ||
|
||||||
|
req.ip ||
|
||||||
|
req._remoteAddress ||
|
||||||
|
(req.connection && req.connection.remoteAddress) ||
|
||||||
|
undefined;
|
||||||
|
req._remoteAddress = ip;
|
||||||
|
return ip;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isDev) {
|
||||||
|
// app.use(morgan('dev' ));
|
||||||
|
}
|
||||||
|
app.use(
|
||||||
|
morgan('combined', {
|
||||||
|
stream: accessLogStream,
|
||||||
|
skip: function(req, res) {
|
||||||
|
return req.method === 'HEAD';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
app.use(
|
||||||
|
morgan('combined', {
|
||||||
|
stream: errorLogStream,
|
||||||
|
skip: function(req, res) {
|
||||||
|
return res.statusCode < 400;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
app.get('/robots.txt', function(req, res) {
|
||||||
|
res.type('text/plain');
|
||||||
|
res.send('User-agent: *\nDisallow: /agent/');
|
||||||
|
});
|
||||||
|
|
||||||
|
// -- We don't want to serve sessions for static resources
|
||||||
|
// -- Save database write on every resources
|
||||||
|
app.use(compress());
|
||||||
|
app.use(express.static(config.root + '/public'));
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
morgan('combined', {
|
||||||
|
stream: requestLogStream,
|
||||||
|
skip: function(req, res) {
|
||||||
|
return req.method === 'HEAD';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
app.use(bodyParser.json({ limit: '5mb' }));
|
||||||
|
app.use(
|
||||||
|
bodyParser.urlencoded({
|
||||||
|
limit: '5mb',
|
||||||
|
parameterLimit: 50000,
|
||||||
|
extended: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
app.use(expressValidator());
|
||||||
|
app.use(cookieParser(config.secret));
|
||||||
|
app.use(
|
||||||
|
session({
|
||||||
|
secret: config.secret,
|
||||||
|
resave: false,
|
||||||
|
saveUninitialized: false,
|
||||||
|
name: config.session_name,
|
||||||
|
store: new MongoStore({
|
||||||
|
url: config.db_admin,
|
||||||
|
autoReconnect: true,
|
||||||
|
collection: 'garfield_sessions'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
);
|
||||||
|
app.use(flash());
|
||||||
|
app.use(expressUtils());
|
||||||
|
app.use(methodOverride());
|
||||||
|
app.use(function(req, res, next) {
|
||||||
|
res.locals.user = req.user;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.all('/uploads', function(req, res, next) {
|
||||||
|
res.header('Access-Control-Allow-Origin', '*');
|
||||||
|
res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
|
||||||
|
res.header('Access-Control-Allow-Headers', 'X-Requested-With');
|
||||||
|
res.header('Access-Control-Allow-Headers', 'Content-Type');
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
app.use('/', routes);
|
||||||
|
|
||||||
|
app.use(function(req, res, next) {
|
||||||
|
const err = new Error('未找到您要访问的页面 (´・_・`)');
|
||||||
|
err.status = 404;
|
||||||
|
// next(err);
|
||||||
|
err.status = err.status || 500;
|
||||||
|
if (err.status !== 404) {
|
||||||
|
logger.error(err);
|
||||||
|
}
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
app.use(function(err, req, res, next) {
|
||||||
|
logger.error({
|
||||||
|
method: req.method,
|
||||||
|
path: req.path,
|
||||||
|
err_status: err.status,
|
||||||
|
err_message: err.message
|
||||||
|
});
|
||||||
|
if (req.path.startsWith('/api')) {
|
||||||
|
res.json({ errcode: 10, errmsg: err.message });
|
||||||
|
} else {
|
||||||
|
res.render('error', {
|
||||||
|
message: err.status === 404 ? err.message : '服务器君开小差啦 @(・●・)@',
|
||||||
|
error: err,
|
||||||
|
title: err.status
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
11
src/controllers/common/index.js
Normal file
11
src/controllers/common/index.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import {Router} from 'express';
|
||||||
|
|
||||||
|
const router = new Router();
|
||||||
|
|
||||||
|
router.get('/test', async(req, res, next) => {
|
||||||
|
res.send({
|
||||||
|
msg: 'test!'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router;
|
149
src/models/admin/Admin.js
Normal file
149
src/models/admin/Admin.js
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
'use strict';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import passportLocalMongoose from 'passport-local-mongoose';
|
||||||
|
|
||||||
|
|
||||||
|
const AdminActionSchema = new mongoose.Schema({
|
||||||
|
_id: {type: String, required: true},
|
||||||
|
name: {type: String, required: true},
|
||||||
|
paths: [{
|
||||||
|
method: String,
|
||||||
|
path: String,
|
||||||
|
}],
|
||||||
|
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
deleted_time: {type: Date},
|
||||||
|
}, {
|
||||||
|
collection: 'admin_actions',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const AdminAction = mongoose.model('AdminAction', AdminActionSchema);
|
||||||
|
|
||||||
|
const AdminRoleSchema = new mongoose.Schema({
|
||||||
|
_id: {type: String, required: true},
|
||||||
|
name: {type: String, required: true},
|
||||||
|
permissions: [{type: String, ref: 'AdminAction'}],
|
||||||
|
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
deleted_time: {type: Date},
|
||||||
|
}, {
|
||||||
|
collection: 'admin_roles',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const AdminRole = mongoose.model('AdminRole', AdminRoleSchema);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户表不需要添加 timestamps: true
|
||||||
|
*
|
||||||
|
* 因为 createdAt 可以从ObjectID中提取, updatedAt 会因为last和ateemps随时变更,
|
||||||
|
* 如果需要跟踪关键数据的变更时间, 应添加独立自管的 updatedAt,
|
||||||
|
* 或者直接使用 immutable data 追踪变化
|
||||||
|
*/
|
||||||
|
const AdminSchema = new mongoose.Schema({
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
|
||||||
|
roles: [{type: String, ref: 'AdminRole'}],
|
||||||
|
permissions: [{type: String, ref: 'AdminAction'}],
|
||||||
|
games: [{type: String}],
|
||||||
|
profile: {
|
||||||
|
name: String,
|
||||||
|
gender: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
comment: String,
|
||||||
|
|
||||||
|
createdBy: {type: String, ref: 'Admin', required: true},
|
||||||
|
lastModifiedBy: {type: String, ref: 'Admin', required: true},
|
||||||
|
|
||||||
|
locked: {type: Boolean, default: false},
|
||||||
|
locked_time: {type: Date},
|
||||||
|
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
|
||||||
|
lastLogin: Date, // passport-local-mongoose 添加的 last 字段记录的是最后一次登录尝试,
|
||||||
|
// 而不是最后一次成功登录
|
||||||
|
});
|
||||||
|
|
||||||
|
AdminSchema.virtual('createdAt').get(function() {
|
||||||
|
return this._id.getTimestamp();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO this is far away from elegant, maybe change it later.
|
||||||
|
*/
|
||||||
|
AdminSchema.virtual('hasSysAdmin').get(function() {
|
||||||
|
return this.roles.includes('sys_admin');
|
||||||
|
});
|
||||||
|
AdminSchema.virtual('hasNavSystem').get(function() {
|
||||||
|
let yes = this.roles.includes('admin');
|
||||||
|
yes = yes || this.permissions.includes('edit_accounts');
|
||||||
|
return yes;
|
||||||
|
});
|
||||||
|
|
||||||
|
AdminSchema.virtual('hasNavGameApi').get(function() {
|
||||||
|
let yes = this.roles.includes('admin');
|
||||||
|
yes = yes || this.roles.includes('game_api_manager');
|
||||||
|
yes = yes || this.permissions.includes('edit_game_apis');
|
||||||
|
return yes;
|
||||||
|
});
|
||||||
|
|
||||||
|
AdminSchema.virtual('hasNavRedis').get(function() {
|
||||||
|
let yes = this.roles.includes('admin');
|
||||||
|
yes = yes || this.roles.includes('redis_manager');
|
||||||
|
yes = yes || this.permissions.includes('edit_redis');
|
||||||
|
return yes;
|
||||||
|
});
|
||||||
|
|
||||||
|
AdminSchema.virtual('hasNavWechat').get(function() {
|
||||||
|
let yes = this.roles.includes('admin');
|
||||||
|
yes = yes || this.roles.includes('wechat_manager');
|
||||||
|
yes = yes || this.permissions.includes('edit_wechat');
|
||||||
|
return yes;
|
||||||
|
});
|
||||||
|
|
||||||
|
AdminSchema.virtual('hasNavGM').get(function() {
|
||||||
|
let yes = this.roles.includes('admin');
|
||||||
|
yes = yes || this.roles.includes('gm_manager');
|
||||||
|
yes = yes || this.permissions.includes('edit_gm');
|
||||||
|
return yes;
|
||||||
|
});
|
||||||
|
|
||||||
|
AdminSchema.virtual('hasNavTool').get(function() {
|
||||||
|
let yes = this.roles.includes('admin');
|
||||||
|
yes = yes || this.roles.includes('tool_manager');
|
||||||
|
yes = yes || this.permissions.includes('edit_tool');
|
||||||
|
return yes;
|
||||||
|
});
|
||||||
|
|
||||||
|
AdminSchema.virtual('hasAdminRole').get(function() {
|
||||||
|
return this.roles.includes('admin');
|
||||||
|
});
|
||||||
|
|
||||||
|
AdminSchema.methods.checkGame = function(gid) {
|
||||||
|
let yes = this.roles.includes('admin');
|
||||||
|
yes = yes || this.games.includes(gid);
|
||||||
|
return yes;
|
||||||
|
};
|
||||||
|
|
||||||
|
AdminSchema.plugin(passportLocalMongoose,
|
||||||
|
{
|
||||||
|
limitAttempts: true,
|
||||||
|
usernameLowerCase: true,
|
||||||
|
maxAttempts: 10,
|
||||||
|
errorMessages: {
|
||||||
|
MissingPasswordError: '请输入密码',
|
||||||
|
AttemptTooSoonError: '您的账户当前被锁定,请稍后再试',
|
||||||
|
TooManyAttemptsError: '您的账户因错误登录次数太多而被锁定',
|
||||||
|
NoSaltValueStoredError: 'Authentication not possible. No salt value stored',
|
||||||
|
IncorrectPasswordError: '用户名或密码错误',
|
||||||
|
IncorrectUsernameError: '用户名或密码错误',
|
||||||
|
MissingUsernameError: '请输入用户名',
|
||||||
|
UserExistsError: '您输入的用户名已被使用',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const Admin = mongoose.model('Admin', AdminSchema);
|
||||||
|
export {Admin, AdminRole, AdminAction};
|
31
src/models/admin/AdminLog.js
Normal file
31
src/models/admin/AdminLog.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
'use strict';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
|
||||||
|
const Schema = mongoose.Schema;
|
||||||
|
const ObjectId = Schema.Types.ObjectId;
|
||||||
|
/**
|
||||||
|
* 操作日志
|
||||||
|
*/
|
||||||
|
const AdminLog = new mongoose.Schema({
|
||||||
|
// 游戏id
|
||||||
|
admin: {type: ObjectId, ref: 'Admin'},
|
||||||
|
username: {type: String},
|
||||||
|
method: {type: String},
|
||||||
|
show_name: {type: String},
|
||||||
|
// 请求路径
|
||||||
|
path: {type: String},
|
||||||
|
user_agent: {type: String},
|
||||||
|
referer: {type: String},
|
||||||
|
// 请求的param
|
||||||
|
params: {type: Schema.Types.Mixed},
|
||||||
|
// ip
|
||||||
|
ip: {type: String},
|
||||||
|
// 备注
|
||||||
|
comment: {type: String},
|
||||||
|
}, {
|
||||||
|
collection: 'admin_logs',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default mongoose.model('AdminLog', AdminLog);
|
||||||
|
|
30
src/models/admin/Game.js
Normal file
30
src/models/admin/Game.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
'use strict';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏信息
|
||||||
|
*/
|
||||||
|
const Game = new mongoose.Schema({
|
||||||
|
// 游戏id
|
||||||
|
game_id: {type: String},
|
||||||
|
// 游戏名
|
||||||
|
name: {type: String},
|
||||||
|
// 英文名
|
||||||
|
name_en: {type: String},
|
||||||
|
// 状态
|
||||||
|
status: {type: String},
|
||||||
|
// 平台
|
||||||
|
platform: {type: String},
|
||||||
|
// 备注
|
||||||
|
comment: {type: String},
|
||||||
|
createdBy: {type: String, ref: 'Admin'},
|
||||||
|
lastModifiedBy: {type: String, ref: 'Admin'},
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
}, {
|
||||||
|
collection: 'games',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default mongoose.model('Game', Game);
|
||||||
|
|
106
src/models/admin/SystemDic.js
Normal file
106
src/models/admin/SystemDic.js
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
'use strict';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典表
|
||||||
|
*/
|
||||||
|
const Schema = mongoose.Schema;
|
||||||
|
|
||||||
|
const SystemDicSchema = new mongoose.Schema({
|
||||||
|
key: {type: String},
|
||||||
|
value: {type: Schema.Types.Mixed},
|
||||||
|
/**
|
||||||
|
* 类型
|
||||||
|
* system: 字典类型
|
||||||
|
* platform: 平台
|
||||||
|
* game_cfg: 游戏配置项目
|
||||||
|
* share_cfg: 游戏分享类型
|
||||||
|
* game_type: 游戏类型
|
||||||
|
* game_status: 游戏状态
|
||||||
|
* */
|
||||||
|
type: {type: String},
|
||||||
|
// 备注
|
||||||
|
comment: {type: String},
|
||||||
|
createdBy: {type: String, ref: 'Admin'},
|
||||||
|
lastModifiedBy: {type: String, ref: 'Admin'},
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
deletedBy: {type: String, ref: 'Admin'},
|
||||||
|
}, {
|
||||||
|
collection: 'system_dics',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* */
|
||||||
|
class SystemDicClass {
|
||||||
|
/**
|
||||||
|
* 获取某一个类型的字典的Map
|
||||||
|
* @param {string} type, 字典类型
|
||||||
|
* @return {Promise} data
|
||||||
|
* */
|
||||||
|
static getDicMap(type) {
|
||||||
|
return this.find({type: type, deleted: false})
|
||||||
|
.then((records) => {
|
||||||
|
const map = new Map();
|
||||||
|
for (const record of records) {
|
||||||
|
map.set(record.key, record.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 生成查询条件
|
||||||
|
* @param {Object} req
|
||||||
|
* @return {Object} opt
|
||||||
|
* @return {Object} sortObj
|
||||||
|
* */
|
||||||
|
static generateQueryOpt(req) {
|
||||||
|
let opt = {};
|
||||||
|
const body = req.body;
|
||||||
|
const order = body.order;
|
||||||
|
const sort = body.sort ? body.sort : 'createdAt';
|
||||||
|
const sortObj = {};
|
||||||
|
sortObj[sort] = order === 'asc' ? 1 : -1;
|
||||||
|
if (body.keyStr) {
|
||||||
|
const orArr = [
|
||||||
|
{key: {$regex: body.keyStr, $options: 'i'}},
|
||||||
|
{value: {$regex: body.keyStr, $options: 'i'}},
|
||||||
|
];
|
||||||
|
opt = {$or: orArr};
|
||||||
|
}
|
||||||
|
if (body.type && body.type !== 'all') {
|
||||||
|
opt.type = body.type;
|
||||||
|
}
|
||||||
|
opt.deleted = false;
|
||||||
|
return {opt, sortObj};
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 保存
|
||||||
|
* @param {Object} req
|
||||||
|
* */
|
||||||
|
static async saveWithReq(req) {
|
||||||
|
const body = req.body;
|
||||||
|
const data = body.data;
|
||||||
|
const ignoreKeySet = new Set(['createdAt', 'updatedAt', '__v', 'deleted', 'delete_time', 'deletedBy']);
|
||||||
|
let record;
|
||||||
|
if (data._id) {
|
||||||
|
record = await this.findById(data._id);
|
||||||
|
}
|
||||||
|
if (!record) {
|
||||||
|
record = new SystemDicModel({
|
||||||
|
createdBy: req.user.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const key in body.data) {
|
||||||
|
if ({}.hasOwnProperty.call(body.data, key) && !ignoreKeySet.has(key)) {
|
||||||
|
record[key] = body.data[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return record.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SystemDicSchema.loadClass(SystemDicClass);
|
||||||
|
|
||||||
|
const SystemDicModel = mongoose.model('SystemDic', SystemDicSchema);
|
||||||
|
export default SystemDicModel;
|
17
src/models/admin/TemplateMsgRecord.js
Normal file
17
src/models/admin/TemplateMsgRecord.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
'use strict';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模版消息发送日志
|
||||||
|
*/
|
||||||
|
const TemplateMsgRecord = new mongoose.Schema({
|
||||||
|
// 游戏id
|
||||||
|
app_id: {type: String},
|
||||||
|
open_ids: [{type: String}],
|
||||||
|
}, {
|
||||||
|
collection: 'template_msg_record',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default mongoose.model('TemplateMsgRecord', TemplateMsgRecord);
|
||||||
|
|
50
src/models/beagle/WeappFormID.js
Normal file
50
src/models/beagle/WeappFormID.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
'use strict';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
const Schema = mongoose.Schema;
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
/**
|
||||||
|
* 小程序的Form ID, 用于发送推送消息
|
||||||
|
*/
|
||||||
|
const WeappFormID = new Schema({
|
||||||
|
account: {type: String},
|
||||||
|
// 0: 未支付,1:已使用, -1:已过期
|
||||||
|
status: {type: Number},
|
||||||
|
app_id: {type: String},
|
||||||
|
open_id: {type: String},
|
||||||
|
form_id: {type: String},
|
||||||
|
// 过期时间
|
||||||
|
expire_time: {type: Date},
|
||||||
|
// 发送时间
|
||||||
|
send_time: {type: Date},
|
||||||
|
}, {
|
||||||
|
collection: 'weapp_form_id',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnBeagle();
|
||||||
|
|
||||||
|
const WeappFormIDModel = conn.model('WeappFormID', WeappFormID);
|
||||||
|
|
||||||
|
// 获取当天可用的formId列表, 每个用户一条
|
||||||
|
WeappFormIDModel.getUserRecordsDay = function(appId) {
|
||||||
|
const idSet = new Set();
|
||||||
|
return WeappFormIDModel
|
||||||
|
.find({status: 0, expire_time: {$gt: new Date()}, app_id: appId})
|
||||||
|
.sort({createdAt: 1})
|
||||||
|
.then((records) => {
|
||||||
|
const result = [];
|
||||||
|
for (const record of records) {
|
||||||
|
if (!idSet.has(record.open_id)) {
|
||||||
|
idSet.add(record.open_id);
|
||||||
|
result.push(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
WeappFormIDModel.updateExpireTime = function() {
|
||||||
|
return WeappFormIDModel.where({status: 0, expire_time: {$lte: new Date()}}).updateMany({status: -1}).exec();
|
||||||
|
};
|
||||||
|
export default WeappFormIDModel;
|
||||||
|
|
||||||
|
|
32
src/models/dalmatian/ShortUrl.js
Normal file
32
src/models/dalmatian/ShortUrl.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
'use strict';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import shortid from 'shortid';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
|
||||||
|
const Schema = mongoose.Schema;
|
||||||
|
/**
|
||||||
|
* 短链接
|
||||||
|
*/
|
||||||
|
const ShortUrl = new mongoose.Schema({
|
||||||
|
_id: {type: String, default: shortid.generate},
|
||||||
|
// 类型,page类型跳转实际url,weapp类型的话,real_url存储宣传图
|
||||||
|
type: {type: String, required: true, default: 'page'},
|
||||||
|
// 真实链接
|
||||||
|
real_url: {type: String},
|
||||||
|
image_url: {type: String},
|
||||||
|
data: {type: Schema.Types.Mixed},
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
deletedBy: {type: String, ref: 'Admin'},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
createdBy: {type: String, ref: 'Admin', required: true},
|
||||||
|
lastModifiedBy: {type: String, ref: 'Admin', required: true},
|
||||||
|
// 备注
|
||||||
|
comment: {type: String},
|
||||||
|
}, {
|
||||||
|
collection: 'short_url',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnDalmatian();
|
||||||
|
|
||||||
|
export default conn.model('ShortUrl', ShortUrl);
|
112
src/models/ghost/Account.js
Normal file
112
src/models/ghost/Account.js
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
'use strict';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
|
||||||
|
const Schema = mongoose.Schema;
|
||||||
|
/**
|
||||||
|
* 游戏账户
|
||||||
|
*/
|
||||||
|
const Account = new Schema({
|
||||||
|
// 公众号获取的原始信息
|
||||||
|
wechat_info: {
|
||||||
|
wechat_id: {type: String},
|
||||||
|
avatar: {type: String},
|
||||||
|
nickname: {type: String},
|
||||||
|
sex: {type: String},
|
||||||
|
province: {type: String},
|
||||||
|
city: {type: String},
|
||||||
|
},
|
||||||
|
// 金蚕游戏id
|
||||||
|
account_id: {type: String},
|
||||||
|
session_id: {type: String},
|
||||||
|
// 从公众号中获取的union_id
|
||||||
|
union_id: {type: String},
|
||||||
|
// 对应的open_id
|
||||||
|
open_id: {type: String},
|
||||||
|
comment: {type: String},
|
||||||
|
locked: {type: Boolean, default: false},
|
||||||
|
locked_time: {type: Date},
|
||||||
|
// 当前积分
|
||||||
|
score: {type: Number, default: 0},
|
||||||
|
// 账户类型,user: 普通用户
|
||||||
|
account_type: {type: String, default: 'user'},
|
||||||
|
// 如果account的 is_new为true, 且invite_user等于等于用户id, 则表明该用户是这个人邀请的
|
||||||
|
is_new: {type: Boolean, default: true},
|
||||||
|
invite_user: {type: String, ref: 'Account'},
|
||||||
|
last_login: {type: Date},
|
||||||
|
last_check: {type: Date},
|
||||||
|
// 收件人信息
|
||||||
|
address_info: {
|
||||||
|
// 地区代码
|
||||||
|
code: {type: String},
|
||||||
|
// 省
|
||||||
|
province: {type: String},
|
||||||
|
// 市
|
||||||
|
city: {type: String},
|
||||||
|
// 区域
|
||||||
|
district: {type: String},
|
||||||
|
// 地址
|
||||||
|
address: {type: String},
|
||||||
|
// 收件人
|
||||||
|
name: {type: String},
|
||||||
|
// 联系电话
|
||||||
|
mobile: {type: String},
|
||||||
|
// 邮编
|
||||||
|
postcode: {type: String},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Account.virtual('createdAt').get(function() {
|
||||||
|
return this._id.getTimestamp();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Account.virtual('sex_name').get(function() {
|
||||||
|
if (this.wechat_info.sex === '1') {
|
||||||
|
return '男';
|
||||||
|
} else if (this.sex === '2') {
|
||||||
|
return '女';
|
||||||
|
} else {
|
||||||
|
return '未指定';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnGhost();
|
||||||
|
const AccountModel = conn.model('Account', Account);
|
||||||
|
|
||||||
|
|
||||||
|
AccountModel.getByOpenId = (openId) => {
|
||||||
|
return AccountModel.findOne({open_id: openId});
|
||||||
|
};
|
||||||
|
|
||||||
|
AccountModel.getByAccountId = (accountId) => {
|
||||||
|
return AccountModel.findOne({account_id: accountId});
|
||||||
|
};
|
||||||
|
|
||||||
|
AccountModel.updateInfo = (record, body) => {
|
||||||
|
const wechatInfo = record.wechat_info || {};
|
||||||
|
if (body.nickname) {
|
||||||
|
wechatInfo.nickname = body.nickname;
|
||||||
|
}
|
||||||
|
if (body.avatar) {
|
||||||
|
wechatInfo.avatar = body.avatar;
|
||||||
|
}
|
||||||
|
if (body.province) {
|
||||||
|
wechatInfo.province = body.province;
|
||||||
|
}
|
||||||
|
if (body.city) {
|
||||||
|
wechatInfo.city = body.city;
|
||||||
|
}
|
||||||
|
if (body.gender !== undefined) {
|
||||||
|
wechatInfo.gender = body.gender;
|
||||||
|
}
|
||||||
|
record.wechat_info = wechatInfo;
|
||||||
|
if (body.union_id) {
|
||||||
|
record.union_id = body.union_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return record;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export default AccountModel;
|
32
src/models/ghost/AppInfo.js
Normal file
32
src/models/ghost/AppInfo.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
|
||||||
|
const Schema = mongoose.Schema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 积分墙app
|
||||||
|
* */
|
||||||
|
const AppInfo = new Schema({
|
||||||
|
name: {type: String},
|
||||||
|
// 已经有多少人领取
|
||||||
|
count: {type: Number, default: 0},
|
||||||
|
appid: {type: String},
|
||||||
|
// 积分
|
||||||
|
score: {type: Number},
|
||||||
|
desc: {type: String},
|
||||||
|
// 图标
|
||||||
|
icon_img: {type: String},
|
||||||
|
sort: {type: Number},
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
}, {
|
||||||
|
collection: 'app_info',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnGhost();
|
||||||
|
const AppInfoModel = conn.model('AppInfo', AppInfo);
|
||||||
|
|
||||||
|
export default AppInfoModel;
|
68
src/models/ghost/CardInfo.js
Normal file
68
src/models/ghost/CardInfo.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
|
||||||
|
const Schema = mongoose.Schema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 贺卡信息
|
||||||
|
* */
|
||||||
|
const CardInfo = new Schema({
|
||||||
|
name: {type: String},
|
||||||
|
// 已经有多少人完成纺织
|
||||||
|
count: {type: Number, default: 0},
|
||||||
|
count_current: {type: Number, default: 0},
|
||||||
|
// 所需积分
|
||||||
|
score: {type: Number},
|
||||||
|
// 卡片故事
|
||||||
|
desc: {type: String},
|
||||||
|
// 图标
|
||||||
|
icon_img: {type: String},
|
||||||
|
// 状态, 0: 正常状态, 1: 已下架
|
||||||
|
status: {type: Number},
|
||||||
|
content_img: {type: String},
|
||||||
|
images: [{type: String}],
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
sort_index: {type: Number},
|
||||||
|
createdBy: {type: String},
|
||||||
|
}, {
|
||||||
|
collection: 'card_info',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnGhost();
|
||||||
|
const CardInfoModel = conn.model('CardInfo', CardInfo);
|
||||||
|
|
||||||
|
CardInfoModel.parse_req = (req, record) => {
|
||||||
|
if (!record) {
|
||||||
|
record = new CardInfoModel({
|
||||||
|
createdBy: req.user.id,
|
||||||
|
count: 0,
|
||||||
|
score: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const body = req.body;
|
||||||
|
record.name = body.name;
|
||||||
|
record.desc = body.desc;
|
||||||
|
record.score = body.score;
|
||||||
|
record.count = body.count;
|
||||||
|
record.images = body.images;
|
||||||
|
record.content_img = body.content_img;
|
||||||
|
record.icon_img = body.icon_img;
|
||||||
|
record.status = body.status;
|
||||||
|
record.sort_index = body.sort_index;
|
||||||
|
return record;
|
||||||
|
};
|
||||||
|
|
||||||
|
CardInfoModel.edit_validate = () => {
|
||||||
|
return {
|
||||||
|
form: '#card_edit_form',
|
||||||
|
rules: [
|
||||||
|
['name', ['required'], '<strong>卡片名</strong> 不能为空'],
|
||||||
|
['score', ['required'], '<strong>所需积分</strong> 不能为空'],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export default CardInfoModel;
|
257
src/models/ghost/EmulatedGames.js
Normal file
257
src/models/ghost/EmulatedGames.js
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
import stringUtil from '../../utils/string.utils';
|
||||||
|
|
||||||
|
const Schema = mongoose.Schema;
|
||||||
|
|
||||||
|
const EmulatedGames = new Schema({
|
||||||
|
gid: {type: Number, required: true},
|
||||||
|
// 游戏名
|
||||||
|
name: {type: String},
|
||||||
|
/** 游戏类型
|
||||||
|
射击 1
|
||||||
|
格斗 2
|
||||||
|
角色扮演 3
|
||||||
|
动作角色扮演 4
|
||||||
|
赛车 5
|
||||||
|
动作游戏 6
|
||||||
|
策略战棋 7
|
||||||
|
其他 8
|
||||||
|
益智游戏 9,
|
||||||
|
体育游戏 10,
|
||||||
|
冒险游戏 11,
|
||||||
|
模拟战略 12,
|
||||||
|
桌面游戏 13,
|
||||||
|
音乐游戏 14,
|
||||||
|
第一人称射击 15
|
||||||
|
*/
|
||||||
|
type: {type: Number},
|
||||||
|
// 游戏介绍
|
||||||
|
introduce: {type: String},
|
||||||
|
// 游戏语言
|
||||||
|
language: {type: String},
|
||||||
|
// 英文名
|
||||||
|
orgname: {type: String},
|
||||||
|
// 图片数量
|
||||||
|
pic_count: {type: Number},
|
||||||
|
// 图片列表
|
||||||
|
images: [{type: String}],
|
||||||
|
// 标签 以半角逗号作分割
|
||||||
|
taglist: {type: String},
|
||||||
|
// 游戏评分
|
||||||
|
score: {type: Number},
|
||||||
|
// 是否是公开游戏, 公开游戏不需要购买即可玩
|
||||||
|
open: {type: Boolean},
|
||||||
|
// 游戏大类 fc, gba, h5, weapp, ad
|
||||||
|
category: {type: String},
|
||||||
|
// 该字段只对gba游戏有效, 等于2的情况下, 使用第二个模拟器的页面, 其他值使用默认模拟器
|
||||||
|
fixed: {type: Number},
|
||||||
|
// 排序字段, 倒序
|
||||||
|
sortIdx: {type: Number},
|
||||||
|
// 游戏链接, 针对h5游戏和ad等类型, 需要该字段,以供客户端跳转
|
||||||
|
link: {type: String},
|
||||||
|
// 游戏的二维码, 针对weapp类型的游戏, 提供该字段已扫码跳转
|
||||||
|
qrcode: {type: String},
|
||||||
|
// 游戏版本
|
||||||
|
version: {type: String},
|
||||||
|
// 游戏真实的play数量
|
||||||
|
playCount: {type: Number, default: 0},
|
||||||
|
// 游戏显示的play数量
|
||||||
|
showCount: {type: Number, default: 1000},
|
||||||
|
createdBy: {type: String},
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
deletedBy: {type: String},
|
||||||
|
published: {type: Boolean, default: false},
|
||||||
|
publish_time: {type: Date},
|
||||||
|
publishedBy: {type: String},
|
||||||
|
}, {
|
||||||
|
collection: 'games',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const gameTypes = {
|
||||||
|
1: '射击',
|
||||||
|
2: '格斗',
|
||||||
|
3: '角色扮演',
|
||||||
|
4: '动作角色扮演',
|
||||||
|
5: '赛车',
|
||||||
|
6: '动作游戏',
|
||||||
|
7: '策略战棋',
|
||||||
|
8: '其他',
|
||||||
|
9: '益智游戏',
|
||||||
|
10: '体育游戏',
|
||||||
|
11: '冒险游戏',
|
||||||
|
12: '模拟战略',
|
||||||
|
13: '桌面游戏',
|
||||||
|
14: '音乐游戏',
|
||||||
|
15: '第一人称射击',
|
||||||
|
};
|
||||||
|
|
||||||
|
EmulatedGames.pre('save', async function(next) {
|
||||||
|
if (!this.gid) {
|
||||||
|
this.gid = await EmulatedGames.nextGid(this.category);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取显示用的游戏类型
|
||||||
|
* */
|
||||||
|
EmulatedGames.virtual('show_type').get(function() {
|
||||||
|
return gameTypes[this.type];
|
||||||
|
});
|
||||||
|
/**
|
||||||
|
* 返回所有的游戏类型
|
||||||
|
* */
|
||||||
|
EmulatedGames.virtual('all_type').get(function() {
|
||||||
|
return gameTypes;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据gid返回数据
|
||||||
|
* @param {string} gid 游戏的短id
|
||||||
|
* @return {Object} record
|
||||||
|
* */
|
||||||
|
EmulatedGames.query.byGid = function(gid) {
|
||||||
|
return this.where({gid: gid, deleted: false});
|
||||||
|
};
|
||||||
|
|
||||||
|
EmulatedGames.statics.allType = function() {
|
||||||
|
return gameTypes;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 获取下一个可能的gid
|
||||||
|
* @param {string} category 游戏的类别
|
||||||
|
* @return {number} gid 新的游戏id
|
||||||
|
* */
|
||||||
|
EmulatedGames.statics.nextGid = async function(category) {
|
||||||
|
const records = await this.find({category: category}).limit(1).sort({gid: -1});
|
||||||
|
if (records && records.length > 0) {
|
||||||
|
return records[0].gid + 1;
|
||||||
|
} else {
|
||||||
|
if (category === 'gba') {
|
||||||
|
return 1;
|
||||||
|
} else if (category === 'fc') {
|
||||||
|
return 7000001;
|
||||||
|
} else if (category === 'h5') {
|
||||||
|
return 10000001;
|
||||||
|
} else {
|
||||||
|
return 20000001;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
EmulatedGames.statics.allLanguage = function() {
|
||||||
|
return ['中文', '简体中文', '繁体中文', '英文', '日文', '多国语言', '德文', '法文', '欧洲', '其他']
|
||||||
|
};
|
||||||
|
|
||||||
|
EmulatedGames.statics.saveWithReq = async function(req) {
|
||||||
|
const body = req.body;
|
||||||
|
const data = body.data;
|
||||||
|
const ignoreKeySet = new Set(['createdAt', 'updatedAt', '__v', 'deleted', 'delete_time', 'deletedBy']);
|
||||||
|
let record;
|
||||||
|
if (data._id) {
|
||||||
|
record = await this.findById(data._id);
|
||||||
|
}
|
||||||
|
if (!record) {
|
||||||
|
record = new EmulatedGameModel({
|
||||||
|
createdBy: req.user.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const key in body.data) {
|
||||||
|
if ({}.hasOwnProperty.call(body.data, key) && !ignoreKeySet.has(key)) {
|
||||||
|
record[key] = body.data[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return record.save();
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @param {Object} req 请求对象
|
||||||
|
* keyStr 关键字
|
||||||
|
* category 游戏类别, all: 所有,fc, gba
|
||||||
|
* type 游戏类型 0: 所有, 其他值: 对应类型的游戏
|
||||||
|
* language 游戏语言
|
||||||
|
* tag 标签
|
||||||
|
* open 是否公开 all: 所有, 0, 1
|
||||||
|
* @return {Object} opt 查询条件对象
|
||||||
|
* @return {Object} sortObj 排序对象
|
||||||
|
* */
|
||||||
|
EmulatedGames.statics.generateQueryOpt = function(req) {
|
||||||
|
let opt = {};
|
||||||
|
const body = req.body;
|
||||||
|
const order = body.order;
|
||||||
|
const sort = body.sort ? body.sort : 'sortIdx';
|
||||||
|
const sortObj = {};
|
||||||
|
sortObj[sort] = order === 'asc' ? 1 : -1;
|
||||||
|
if (body.keyStr) {
|
||||||
|
const orArr = [
|
||||||
|
{name: {$regex: body.keyStr, $options: 'i'}},
|
||||||
|
{orgname: {$regex: body.keyStr, $options: 'i'}},
|
||||||
|
{taglist: {$regex: body.keyStr, $options: 'i'}},
|
||||||
|
];
|
||||||
|
opt = {$or: orArr};
|
||||||
|
}
|
||||||
|
if (body.category && body.category !== 'all') {
|
||||||
|
opt.category = body.category;
|
||||||
|
}
|
||||||
|
if (body.type) {
|
||||||
|
opt.type = body.type;
|
||||||
|
}
|
||||||
|
if (body.language) {
|
||||||
|
opt.language = {$regex: body.language, $options: 'i'};
|
||||||
|
}
|
||||||
|
if (body.tag) {
|
||||||
|
opt.taglist = {$regex: body.tag, $options: 'i'};
|
||||||
|
}
|
||||||
|
if (body.open && body.open !== 'all') {
|
||||||
|
opt.open = stringUtil.isTrue(body.open);
|
||||||
|
}
|
||||||
|
if (body.published !== undefined && body.published !== 'all') {
|
||||||
|
opt.published = stringUtil.isTrue(body.published);
|
||||||
|
}
|
||||||
|
opt.deleted = false;
|
||||||
|
return {opt, sortObj};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前最大的sortIdx
|
||||||
|
* */
|
||||||
|
EmulatedGames.statics.getMaxIndex = async function() {
|
||||||
|
const records = await this.find({deleted: false}).limit(1).sort({sortIdx: -1});
|
||||||
|
if (records.length > 0) {
|
||||||
|
return records[0].sortIdx;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 获取当前数值的排名
|
||||||
|
* @param {Number} idx 当前的sortIdx
|
||||||
|
* @param {Number} gid 游戏的gid
|
||||||
|
* */
|
||||||
|
EmulatedGames.statics.currentSort = async function(idx, gid) {
|
||||||
|
return await this.countDocuments({deleted: false, sortIdx: {$gte: idx}, gid: {$lt: gid}});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新游戏的发布状态
|
||||||
|
* @param {Array} ids
|
||||||
|
* @param {Boolean} status
|
||||||
|
* @param {String} user
|
||||||
|
* */
|
||||||
|
EmulatedGames.statics.updatePublishState = async function(ids, status, user) {
|
||||||
|
if (status) {
|
||||||
|
return await this.where({_id: {$in: ids}}).updateMany({
|
||||||
|
published: true, publish_time: new Date, publishedBy: user});
|
||||||
|
} else {
|
||||||
|
return await this.where({_id: {$in: ids}}).updateMany({published: false});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnGhost();
|
||||||
|
|
||||||
|
const EmulatedGameModel = conn.model('EmulatedGames', EmulatedGames);
|
||||||
|
|
||||||
|
export default EmulatedGameModel;
|
76
src/models/ghost/ExchangeRecord.js
Normal file
76
src/models/ghost/ExchangeRecord.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
|
||||||
|
const Schema = mongoose.Schema;
|
||||||
|
/**
|
||||||
|
* 公仔兑换&物流记录
|
||||||
|
* */
|
||||||
|
const ExchangeRecord = new Schema({
|
||||||
|
user: {type: String, ref: 'Account'},
|
||||||
|
// 接受公仔的用户
|
||||||
|
receive_user: {type: String, ref: 'Account'},
|
||||||
|
// 公仔信息
|
||||||
|
item: {type: String, ref: 'Gift'},
|
||||||
|
// 兑换类型, 0: 自己兑换, 1: 赠送给朋友,并且填写了配送地址, 2: 赠送给朋友, 未填写配送地址
|
||||||
|
type: {type: Number, default: 0},
|
||||||
|
// 兑换消耗积分
|
||||||
|
score: {type: Number},
|
||||||
|
// 寄语
|
||||||
|
content: {type: String},
|
||||||
|
// 收件人
|
||||||
|
target_name: {type: String},
|
||||||
|
// 署名
|
||||||
|
sign: {type: String},
|
||||||
|
// 收件人信息, 从account中继承
|
||||||
|
address_info: {
|
||||||
|
// 地区代码
|
||||||
|
code: {type: String},
|
||||||
|
// 省
|
||||||
|
province: {type: String},
|
||||||
|
// 市
|
||||||
|
city: {type: String},
|
||||||
|
// 区域
|
||||||
|
district: {type: String},
|
||||||
|
// 地址
|
||||||
|
address: {type: String},
|
||||||
|
// 收件人
|
||||||
|
name: {type: String},
|
||||||
|
// 联系电话
|
||||||
|
mobile: {type: String},
|
||||||
|
// 邮编
|
||||||
|
postcode: {type: String},
|
||||||
|
},
|
||||||
|
// 配送时间
|
||||||
|
send_time: {type: Date},
|
||||||
|
// 物流公司名称
|
||||||
|
company: {type: String},
|
||||||
|
// 快递单号
|
||||||
|
invoice: {type: String},
|
||||||
|
// 状态 0: 未领取, 1: 已领取, -1: 已退回, 2: 已配送
|
||||||
|
status: {type: Number, default: 0},
|
||||||
|
}, {
|
||||||
|
collection: 'exchange_record',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
ExchangeRecord.virtual('statusStr').get(function() {
|
||||||
|
switch (this.status) {
|
||||||
|
case -1:
|
||||||
|
return '已退回';
|
||||||
|
case 1:
|
||||||
|
return '未发货';
|
||||||
|
case 0:
|
||||||
|
return '未领取';
|
||||||
|
case 2:
|
||||||
|
return '已发货';
|
||||||
|
case -2:
|
||||||
|
return '服务端退回';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnGhost();
|
||||||
|
const ExchangeRecordModel = conn.model('ExchangeRecord', ExchangeRecord);
|
||||||
|
|
||||||
|
export default ExchangeRecordModel;
|
87
src/models/ghost/Gift.js
Normal file
87
src/models/ghost/Gift.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
|
||||||
|
const Schema = mongoose.Schema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 公仔信息
|
||||||
|
* */
|
||||||
|
const Gift = new Schema({
|
||||||
|
// 公仔名
|
||||||
|
name: {type: String},
|
||||||
|
// 详细说明
|
||||||
|
desc: {type: String},
|
||||||
|
// 大小
|
||||||
|
size: {type: String},
|
||||||
|
// 总数
|
||||||
|
amount: {type: Number},
|
||||||
|
// 已兑换数量
|
||||||
|
amount_used: {type: Number},
|
||||||
|
// 当前正在编织数量
|
||||||
|
amount_current: {type: Number, default: 0},
|
||||||
|
// 所需积分
|
||||||
|
score: {type: Number},
|
||||||
|
// 实物图, 大图
|
||||||
|
main_img: {type: String},
|
||||||
|
// 大图
|
||||||
|
content_img: {type: String},
|
||||||
|
// 介绍图片列表
|
||||||
|
images: [{type: String}],
|
||||||
|
// 小图
|
||||||
|
icon_img: {type: String},
|
||||||
|
areas: [{type: String}],
|
||||||
|
// 状态, 0: 正常状态, 1: 已下架, 2: 往期商品
|
||||||
|
status: {type: Number},
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
// 序号
|
||||||
|
sort_index: {type: Number},
|
||||||
|
createdBy: {type: String},
|
||||||
|
}, {
|
||||||
|
collection: 'gifts',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnGhost();
|
||||||
|
const GiftModel = conn.model('Gift', Gift);
|
||||||
|
|
||||||
|
GiftModel.parse_gift = (req, record) => {
|
||||||
|
if (!record) {
|
||||||
|
record = new GiftModel({
|
||||||
|
createdBy: req.user.id,
|
||||||
|
amount: 0,
|
||||||
|
amount_used: 0,
|
||||||
|
score: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const body = req.body;
|
||||||
|
record.name = body.name;
|
||||||
|
record.desc = body.desc;
|
||||||
|
record.size = body.size;
|
||||||
|
record.amount = body.amount;
|
||||||
|
record.amount_used = body.amount_used;
|
||||||
|
record.score = body.score;
|
||||||
|
record.main_img = body.main_img;
|
||||||
|
record.content_img = body.content_img;
|
||||||
|
record.images = body.images;
|
||||||
|
record.areas = body.areas;
|
||||||
|
record.icon_img = body.icon_img;
|
||||||
|
record.status = body.status;
|
||||||
|
record.sort_index = body.sort_index;
|
||||||
|
return record;
|
||||||
|
};
|
||||||
|
|
||||||
|
GiftModel.gift_edit_validate = () => {
|
||||||
|
return {
|
||||||
|
form: '#gift_edit_form',
|
||||||
|
rules: [
|
||||||
|
['name', ['required'], '<strong>公仔名</strong> 不能为空'],
|
||||||
|
['score', ['required'], '<strong>所需积分</strong> 不能为空'],
|
||||||
|
['amount', ['required'], '<strong>库存总数</strong> 不能为空'],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GiftModel;
|
34
src/models/ghost/Message.js
Normal file
34
src/models/ghost/Message.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
|
||||||
|
const Schema = mongoose.Schema;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 消息记录
|
||||||
|
* */
|
||||||
|
const Message = new Schema({
|
||||||
|
// 帐号信息
|
||||||
|
user: {type: String, ref: 'Account'},
|
||||||
|
msg: {type: String},
|
||||||
|
// 消息状态, 0: 未下发, 1: 已下发
|
||||||
|
status: {type: Number},
|
||||||
|
}, {
|
||||||
|
collection: 'messages',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnGhost();
|
||||||
|
const MessageModel = conn.model('Message', Message);
|
||||||
|
|
||||||
|
MessageModel.addOneRecord = function(uid, msg) {
|
||||||
|
const record = new MessageModel({
|
||||||
|
user: uid,
|
||||||
|
msg: msg,
|
||||||
|
status: 0,
|
||||||
|
});
|
||||||
|
return record.save();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MessageModel;
|
62
src/models/ghost/RecommendGames.js
Normal file
62
src/models/ghost/RecommendGames.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
|
||||||
|
const Schema = mongoose.Schema;
|
||||||
|
/**
|
||||||
|
* 推荐游戏
|
||||||
|
* */
|
||||||
|
const RecommendGamesSchema = new Schema({
|
||||||
|
// 游戏id
|
||||||
|
gid: {type: String, ref: 'Games'},
|
||||||
|
// 0: 游戏, 1: 链接
|
||||||
|
type: {type: Number, default: 0},
|
||||||
|
link: {type: String},
|
||||||
|
image: {type: String},
|
||||||
|
name: {type: String},
|
||||||
|
sortIdx: {type: Number, default: 0},
|
||||||
|
createdBy: {type: String},
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
deletedBy: {type: String},
|
||||||
|
}, {
|
||||||
|
collection: 'recommend_games',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
class RecommendGamesClass {
|
||||||
|
/**
|
||||||
|
* 分析request, 保存对记录的更改
|
||||||
|
* @param {Object} req
|
||||||
|
* @return {Promise} record
|
||||||
|
* */
|
||||||
|
static async saveWithReq(req) {
|
||||||
|
const body = req.body;
|
||||||
|
const data = body.data;
|
||||||
|
const ignoreKeySet = new Set(['createdAt', 'updatedAt', '__v', 'deleted', 'delete_time', 'deletedBy']);
|
||||||
|
let record;
|
||||||
|
if (data._id) {
|
||||||
|
record = await this.findById(data._id);
|
||||||
|
}
|
||||||
|
if (!record) {
|
||||||
|
record = new RecommendGamesModel({
|
||||||
|
createdBy: req.user.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (const key in body.data) {
|
||||||
|
if ({}.hasOwnProperty.call(body.data, key) && !ignoreKeySet.has(key)) {
|
||||||
|
record[key] = body.data[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return record.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RecommendGamesSchema.loadClass(RecommendGamesClass);
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnGhost();
|
||||||
|
const RecommendGamesModel = conn.model('RecommendGames', RecommendGamesSchema);
|
||||||
|
|
||||||
|
export default RecommendGamesModel;
|
50
src/models/ghost/ScoreRecord.js
Normal file
50
src/models/ghost/ScoreRecord.js
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import moment from 'moment';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
|
||||||
|
const Schema = mongoose.Schema;
|
||||||
|
/**
|
||||||
|
* 积分变动记录
|
||||||
|
* */
|
||||||
|
const ScoreRecord = new Schema({
|
||||||
|
// 关联的帐号
|
||||||
|
user: {type: String, ref: 'Account'},
|
||||||
|
// 关联的幽灵
|
||||||
|
ghost: {type: String, ref: 'Ghost'},
|
||||||
|
// 关联的兑换记录
|
||||||
|
exchange_record: {type: String, ref: 'ExchangeRecord'},
|
||||||
|
// 卡片兑换记录
|
||||||
|
card_record: {type: String, ref: 'CardRecord'},
|
||||||
|
appid: {type: String},
|
||||||
|
// 日期, YYYY-MM-DD
|
||||||
|
day: {type: String},
|
||||||
|
// 类型:
|
||||||
|
// ghost: 幽灵产生, gift: 看广告加速, ghost_init: 新幽灵初始增加, click_app: 点击积分墙,
|
||||||
|
// exchange: 兑换, card: 兑换卡片, money: 兑换现金, retract: 公仔退回, retract_svr: 系统退回
|
||||||
|
type: {type: String},
|
||||||
|
// 操作的管理员帐号
|
||||||
|
account_admin: {type: String},
|
||||||
|
// 变动数量
|
||||||
|
amount: {type: Number},
|
||||||
|
}, {
|
||||||
|
collection: 'score_record',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
const conn = dbUtil.getConnGhost();
|
||||||
|
const ScoreRecordModel = conn.model('ScoreRecord', ScoreRecord);
|
||||||
|
|
||||||
|
// 添加一条公仔退回记录
|
||||||
|
ScoreRecordModel.addRetractScore = function(uid, amount, eid, adminId) {
|
||||||
|
const record = new ScoreRecordModel({
|
||||||
|
user: uid,
|
||||||
|
amount: amount,
|
||||||
|
exchange_record: eid,
|
||||||
|
account_admin: adminId,
|
||||||
|
type: 'retract_svr',
|
||||||
|
day: moment().format('YYYY-MM-DD'),
|
||||||
|
});
|
||||||
|
return record.save();
|
||||||
|
};
|
||||||
|
export default ScoreRecordModel;
|
23
src/models/snoopy/ChinaArea.js
Normal file
23
src/models/snoopy/ChinaArea.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
'use strict';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中国区域定义
|
||||||
|
*/
|
||||||
|
const ChinaArea = new mongoose.Schema({
|
||||||
|
// 区域名
|
||||||
|
name: {type: String},
|
||||||
|
// 包含地区
|
||||||
|
locations: [{type: String}],
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
}, {
|
||||||
|
collection: 'china_area',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnSnoopy();
|
||||||
|
|
||||||
|
export default conn.model('ChinaArea', ChinaArea);
|
||||||
|
|
45
src/models/snoopy/ChinaRegion.js
Normal file
45
src/models/snoopy/ChinaRegion.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
'use strict';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中国行政区划
|
||||||
|
*/
|
||||||
|
const ChinaRegion = new mongoose.Schema({
|
||||||
|
// 区划id, 6位数字
|
||||||
|
_id: {type: Number},
|
||||||
|
// 等级, 0: 国, 1: 省, 2: 市, 3: 区县
|
||||||
|
level: {type: Number},
|
||||||
|
// 父级id
|
||||||
|
parent_id: {type: Number},
|
||||||
|
// 中文名
|
||||||
|
name: {type: String},
|
||||||
|
// 中文名, 短
|
||||||
|
short_name: {type: String},
|
||||||
|
// 拼音
|
||||||
|
pinyin: {type: String, index: true},
|
||||||
|
// 国家
|
||||||
|
country: {type: String},
|
||||||
|
// 省份, 冗余数据, 方便查找, 全中国总共才2000多条数据, 没什么问题
|
||||||
|
province: {type: String},
|
||||||
|
// 市
|
||||||
|
city: {type: String},
|
||||||
|
// 区县
|
||||||
|
district: {type: String},
|
||||||
|
// 邮编
|
||||||
|
zipcode: {type: String},
|
||||||
|
// 区号
|
||||||
|
citycode: {type: String},
|
||||||
|
// 经度
|
||||||
|
lan: {type: Number},
|
||||||
|
// 维度
|
||||||
|
lat: {type: Number},
|
||||||
|
}, {
|
||||||
|
collection: 'china_region',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnSnoopy();
|
||||||
|
|
||||||
|
export default conn.model('ChinaRegion', ChinaRegion);
|
||||||
|
|
49
src/models/snoopy/CustomerReplay.js
Normal file
49
src/models/snoopy/CustomerReplay.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
'use strict';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客服关键字回复
|
||||||
|
*/
|
||||||
|
const CustomerReplay = new mongoose.Schema({
|
||||||
|
// 游戏id
|
||||||
|
game_id: {type: String},
|
||||||
|
// 响应的关键字
|
||||||
|
keys: [{type: String}],
|
||||||
|
items: [{
|
||||||
|
_id: false,
|
||||||
|
item_id: {type: String},
|
||||||
|
count: {type: Number},
|
||||||
|
}],
|
||||||
|
actived: {type: Boolean, default: true},
|
||||||
|
// 备注
|
||||||
|
comment: {type: String},
|
||||||
|
createdBy: {type: String},
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
deletedBy: {type: String},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
}, {
|
||||||
|
collection: 'customer_replay',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnSnoopy();
|
||||||
|
const CustomerReplayModel = conn.model('CustomerReplay', CustomerReplay);
|
||||||
|
|
||||||
|
CustomerReplayModel.parseReq = function(req, record) {
|
||||||
|
if (!record) {
|
||||||
|
record = new CustomerReplayModel({
|
||||||
|
createdBy: req.user.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const body = req.body;
|
||||||
|
record.game_id = body.game_id;
|
||||||
|
record.keys = body.keys;
|
||||||
|
record.items = body.items;
|
||||||
|
record.actived = body.actived;
|
||||||
|
record.comment = body.comment;
|
||||||
|
return record;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomerReplayModel;
|
||||||
|
|
116
src/models/snoopy/GameInfo.js
Normal file
116
src/models/snoopy/GameInfo.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
'use strict';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 游戏信息
|
||||||
|
*/
|
||||||
|
const GameInfo = new mongoose.Schema({
|
||||||
|
// 游戏id
|
||||||
|
game_id: {type: String},
|
||||||
|
// 游戏名
|
||||||
|
game_name: {type: String},
|
||||||
|
// 英文名
|
||||||
|
game_name_en: {type: String},
|
||||||
|
// 游戏icon
|
||||||
|
game_icon: {type: String},
|
||||||
|
// 游戏类型
|
||||||
|
game_type: {type: Number},
|
||||||
|
// app id
|
||||||
|
app_id: {type: String},
|
||||||
|
// app secret
|
||||||
|
app_secret: {type: String},
|
||||||
|
// 状态
|
||||||
|
status: {type: Number, default: 0},
|
||||||
|
// 平台
|
||||||
|
platform: {type: String},
|
||||||
|
// 关联的游戏列表
|
||||||
|
linked_games: [{type: String, ref: 'GameInfo'}],
|
||||||
|
// 排序
|
||||||
|
sort: {type: Number, index: true},
|
||||||
|
// 是否主推
|
||||||
|
is_recommend: {type: Boolean, default: false},
|
||||||
|
// 是否火爆
|
||||||
|
is_hot: {type: Boolean, default: false},
|
||||||
|
// 是否是新游
|
||||||
|
is_new: {type: Boolean, default: false},
|
||||||
|
// 备注
|
||||||
|
comment: {type: String},
|
||||||
|
// 点击率
|
||||||
|
click_count: {type: Number, default: 0},
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
deletedBy: {type: String},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
}, {
|
||||||
|
collection: 'game_info',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
const showTypes = {
|
||||||
|
0: '益智解密',
|
||||||
|
1: '手脑反应',
|
||||||
|
2: '策略养成',
|
||||||
|
};
|
||||||
|
|
||||||
|
const platforms = {
|
||||||
|
6000: '内部测试',
|
||||||
|
6001: '微信',
|
||||||
|
6002: 'QQ玩一玩',
|
||||||
|
6003: 'OPPO小游戏',
|
||||||
|
6004: 'VIVO快游戏',
|
||||||
|
6501: 'facebook小游戏',
|
||||||
|
6005: '百度',
|
||||||
|
6006: '抖音',
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusObj = {
|
||||||
|
0: '已上线',
|
||||||
|
1: '暂停',
|
||||||
|
2: '新版本开发中',
|
||||||
|
3: '外包开发中',
|
||||||
|
4: '开发中',
|
||||||
|
5: '未开始',
|
||||||
|
6: '测试中',
|
||||||
|
7: '二次开发中',
|
||||||
|
};
|
||||||
|
|
||||||
|
GameInfo.virtual('show_type').get(function() {
|
||||||
|
return showTypes[this.game_type];
|
||||||
|
});
|
||||||
|
|
||||||
|
GameInfo.virtual('all_type').get(function() {
|
||||||
|
return showTypes;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
GameInfo.virtual('platform_show').get(function() {
|
||||||
|
return platforms[this.platform];
|
||||||
|
});
|
||||||
|
|
||||||
|
GameInfo.virtual('status_show').get(function() {
|
||||||
|
return statusObj[this.status];
|
||||||
|
});
|
||||||
|
|
||||||
|
GameInfo.statics = {
|
||||||
|
all_type() {
|
||||||
|
return showTypes;
|
||||||
|
},
|
||||||
|
all_platform() {
|
||||||
|
return platforms;
|
||||||
|
},
|
||||||
|
status_list() {
|
||||||
|
const list = [];
|
||||||
|
for (const key in statusObj) {
|
||||||
|
if ({}.hasOwnProperty.call(statusObj, key)) {
|
||||||
|
list.push({
|
||||||
|
key: key,
|
||||||
|
name: statusObj[key],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const conn = dbUtil.getConnSnoopy();
|
||||||
|
|
||||||
|
export default conn.model('GameInfo', GameInfo);
|
||||||
|
|
97
src/models/snoopy/GameShareImage.js
Normal file
97
src/models/snoopy/GameShareImage.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
'use strict';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
import stringUtil from '../../utils/string.utils';
|
||||||
|
/**
|
||||||
|
* 游戏分享图
|
||||||
|
*/
|
||||||
|
const GameShareImage = new mongoose.Schema({
|
||||||
|
// 游戏id
|
||||||
|
game_id: {type: String},
|
||||||
|
// 是否是默认分享
|
||||||
|
default_share: {type: Boolean, default: false},
|
||||||
|
// 分享类型
|
||||||
|
share_type: {type: String},
|
||||||
|
// 分享图路径
|
||||||
|
share_image: {type: String},
|
||||||
|
// 分享语
|
||||||
|
share_word: {type: String},
|
||||||
|
// 多个分享图
|
||||||
|
share_images: [{type: String}],
|
||||||
|
// 多个分享语
|
||||||
|
share_words: [{type: String}],
|
||||||
|
// 区域
|
||||||
|
area: {type: String},
|
||||||
|
// 地域列表
|
||||||
|
locations: [{type: String}],
|
||||||
|
// 性别
|
||||||
|
sex: {type: String},
|
||||||
|
// 广告id
|
||||||
|
ad_id: {type: String},
|
||||||
|
// 分享次数
|
||||||
|
share_count: {type: Number, default: 0},
|
||||||
|
// 广告次数
|
||||||
|
ad_count: {type: Number, default: 0},
|
||||||
|
// 广告间隔时间
|
||||||
|
ad_cd: {type: Number, default: 0},
|
||||||
|
// 0: 分享图, 1: 广告
|
||||||
|
type: {type: Number, default: 0},
|
||||||
|
// 备注
|
||||||
|
comment: {type: String},
|
||||||
|
createdBy: {type: String},
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
deletedBy: {type: String},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
}, {
|
||||||
|
collection: 'game_share_images',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnSnoopy();
|
||||||
|
const GameShareImageModel = conn.model('GameShareImage', GameShareImage);
|
||||||
|
|
||||||
|
GameShareImageModel.parse_req = (req, record) => {
|
||||||
|
if (!record) {
|
||||||
|
record = new GameShareImageModel({
|
||||||
|
createdBy: req.user.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const body = req.body;
|
||||||
|
record.game_id = body.game_id;
|
||||||
|
record.default_share = stringUtil.isTrue(body.default_share);
|
||||||
|
record.share_type = body.share_type;
|
||||||
|
record.share_images = body.share_images;
|
||||||
|
record.share_words = body.share_words;
|
||||||
|
if (record.share_images && record.share_images.length > 0) {
|
||||||
|
record.share_image = record.share_images[0];
|
||||||
|
}
|
||||||
|
if (record.share_words && record.share_words.length > 0) {
|
||||||
|
record.share_word = record.share_words[0];
|
||||||
|
}
|
||||||
|
record.locations = body.locations;
|
||||||
|
record.sex = body.sex;
|
||||||
|
record.area = body.area;
|
||||||
|
record.comment = body.comment;
|
||||||
|
record.type = body.type;
|
||||||
|
record.ad_id = body.ad_id;
|
||||||
|
record.ad_count = body.ad_count;
|
||||||
|
record.ad_cd = body.ad_cd;
|
||||||
|
record.share_count = body.share_count;
|
||||||
|
return record;
|
||||||
|
};
|
||||||
|
|
||||||
|
GameShareImageModel.edit_validate = () => {
|
||||||
|
return {
|
||||||
|
form: '#edit_form',
|
||||||
|
rules: [
|
||||||
|
['share_type', ['required'], '请选择<strong>分享类型</strong> '],
|
||||||
|
['share_count', ['required'], '<strong>分享次数</strong> 不能为空'],
|
||||||
|
['ad_count', ['required'], '<strong>广告播放次数</strong> 不能为空'],
|
||||||
|
['ad_cd', ['required'], '<strong>广告播放间隔</strong> 不能为空'],
|
||||||
|
['type', ['required'], '请选择<strong>优先级</strong>'],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export default GameShareImageModel;
|
||||||
|
|
||||||
|
|
96
src/models/snoopy/Gift.js
Normal file
96
src/models/snoopy/Gift.js
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
const GiftSchema = new mongoose.Schema({
|
||||||
|
items: [{
|
||||||
|
_id: false,
|
||||||
|
itemid: {type: String},
|
||||||
|
itemnum: {type: Number, default: 0},
|
||||||
|
itemname: {type: String},
|
||||||
|
}],
|
||||||
|
// 礼包名
|
||||||
|
name: {type: String},
|
||||||
|
game_id: {type: String},
|
||||||
|
// 状态
|
||||||
|
status: {type: Number, default: 0},
|
||||||
|
// 备注
|
||||||
|
comment: {type: String},
|
||||||
|
createdBy: {type: String},
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
deletedBy: {type: String},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
}, {
|
||||||
|
collection: 'gift',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* */
|
||||||
|
class GiftClass {
|
||||||
|
/**
|
||||||
|
* 分析request, 保存对记录的更改
|
||||||
|
* @param {Object} req
|
||||||
|
* @param {Object} record
|
||||||
|
* @return {Promise} record
|
||||||
|
* */
|
||||||
|
static parseReq(req, record) {
|
||||||
|
const body = req.body;
|
||||||
|
if (!record) {
|
||||||
|
record = new GiftModel({
|
||||||
|
createdBy: req.user.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
record.game_id = body.game_id;
|
||||||
|
record.name = body.name;
|
||||||
|
record.items = body.items;
|
||||||
|
record.status = body.status;
|
||||||
|
record.comment = body.comment;
|
||||||
|
return record.save();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 分析request, 并生成查询的条件和排序Object
|
||||||
|
* @param {Object} req
|
||||||
|
* @return {Object} opt
|
||||||
|
* @return {Object} sortObj
|
||||||
|
* */
|
||||||
|
static parseGiftQueryOpt(req) {
|
||||||
|
let opt = {};
|
||||||
|
const body = req.body;
|
||||||
|
const keyStr = body.name;
|
||||||
|
let timeBegin = body.timeBegin;
|
||||||
|
let timeEnd = body.timeEnd;
|
||||||
|
const order = body.order;
|
||||||
|
const sort = body.sort ? body.sort : 'createdAt';
|
||||||
|
const sortObj = {sort: order === 'asc' ? 1 : -1};
|
||||||
|
sortObj[sort] = order === 'asc' ? 1 : -1;
|
||||||
|
if (keyStr) {
|
||||||
|
opt = {$or: [
|
||||||
|
{name: {$regex: keyStr, $options: 'i'}},
|
||||||
|
{comment: {$regex: keyStr, $options: 'i'}},
|
||||||
|
{'items.itemname': {$regex: keyStr, $options: 'i'}},
|
||||||
|
]};
|
||||||
|
}
|
||||||
|
(body.gameId) && (opt.game_id = body.gameId);
|
||||||
|
if (timeBegin && !timeEnd) {
|
||||||
|
timeBegin = moment(timeBegin, 'YYYY-MM-DD').startOf('day').format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
opt.createdAt = {$gte: timeBegin};
|
||||||
|
} else if (timeBegin && timeEnd) {
|
||||||
|
timeBegin = moment(timeBegin, 'YYYY-MM-DD').startOf('day').format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
timeEnd = moment(timeEnd, 'YYYY-MM-DD').endOf('day').format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
opt['$and'] = [{createdAt: {$gte: timeBegin}}, {createdAt: {$lte: timeEnd}}];
|
||||||
|
} else if (!timeBegin && timeEnd) {
|
||||||
|
timeEnd = moment(timeEnd, 'YYYY-MM-DD').endOf('day').format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
opt.createdAt = {$lte: timeEnd};
|
||||||
|
}
|
||||||
|
opt.deleted = false;
|
||||||
|
return {opt, sortObj};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GiftSchema.loadClass(GiftClass);
|
||||||
|
|
||||||
|
const GiftModel = dbUtil.getConnSnoopy().model('Gift', GiftSchema);
|
||||||
|
|
||||||
|
export default GiftModel;
|
27
src/models/snoopy/GiftHistory.js
Normal file
27
src/models/snoopy/GiftHistory.js
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 兑换码兑换记录
|
||||||
|
*/
|
||||||
|
const GiftHistory = new mongoose.Schema({
|
||||||
|
// 兑换码
|
||||||
|
gift_no: {type: String},
|
||||||
|
gift: {type: String, ref: 'Gift'},
|
||||||
|
gift_pack: {type: String, ref: 'GiftPack'},
|
||||||
|
account_id: {type: String},
|
||||||
|
// 备注
|
||||||
|
comment: {type: String},
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
deletedBy: {type: String},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
}, {
|
||||||
|
collection: 'gift_history',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnSnoopy();
|
||||||
|
const GiftHistoryModel = conn.model('GiftHistory', GiftHistory);
|
||||||
|
|
||||||
|
export default GiftHistoryModel;
|
||||||
|
|
191
src/models/snoopy/GiftPack.js
Normal file
191
src/models/snoopy/GiftPack.js
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
import stringUtil from '../../utils/string.utils';
|
||||||
|
import moment from 'moment';
|
||||||
|
import Games from './GameInfo';
|
||||||
|
import logger from '../../utils/logger';
|
||||||
|
import Gift from './Gift';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 礼包
|
||||||
|
*/
|
||||||
|
const GiftPack = new mongoose.Schema({
|
||||||
|
// 兑换码
|
||||||
|
gift_nos: [{type: String}],
|
||||||
|
gift_no_count: {type: Number},
|
||||||
|
// 游戏id
|
||||||
|
game_id: {type: String},
|
||||||
|
// 服务器
|
||||||
|
svr_id: {type: String},
|
||||||
|
svr_name: {type: String},
|
||||||
|
// 活动名
|
||||||
|
name: {type: String},
|
||||||
|
gift: {type: String, ref: 'Gift'},
|
||||||
|
// 批次号
|
||||||
|
batch_no: {type: Number, index: true},
|
||||||
|
// 开始时间
|
||||||
|
begin_time: {type: Date},
|
||||||
|
// 结束时间
|
||||||
|
end_time: {type: Date},
|
||||||
|
// 渠道
|
||||||
|
platforms: [{type: String}],
|
||||||
|
// 类型, one: 一次性(只能一人兑换一次), batch: 批量(可供多人兑换,每人一次)
|
||||||
|
type: {type: String},
|
||||||
|
// 状态
|
||||||
|
status: {type: Number, default: 0},
|
||||||
|
// 兑换需要的vip等级
|
||||||
|
vip: {type: Number, default: 0},
|
||||||
|
// 备注
|
||||||
|
comment: {type: String},
|
||||||
|
createdBy: {type: String},
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
deletedBy: {type: String},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
}, {
|
||||||
|
collection: 'gift_pack',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
GiftPack.pre('save', async function(next) {
|
||||||
|
if (!this.batch_no) {
|
||||||
|
let batchNo = await GiftPackModel.nextBatchNo();
|
||||||
|
this.batch_no = batchNo;
|
||||||
|
if (this.gift_no_count > 0) {
|
||||||
|
this.gift_nos = GiftPackModel.generateGiftNos(this.gift_no_count, batchNo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnSnoopy();
|
||||||
|
const GiftPackModel = conn.model('GiftPack', GiftPack);
|
||||||
|
|
||||||
|
// 生成指定数量的兑换码
|
||||||
|
GiftPackModel.generateGiftNos = function(count, batchNo) {
|
||||||
|
const set = new Set();
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const str = GiftPackModel.randomGiftNos(set);
|
||||||
|
set.add(str);
|
||||||
|
}
|
||||||
|
return [...set].map((o)=> batchNo + o);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 生成一条随机的兑换码
|
||||||
|
GiftPackModel.randomGiftNos = function(set) {
|
||||||
|
const num = stringUtil.randomNumAdv(0, 2176782335);
|
||||||
|
const str = stringUtil.string10to36(num).padStart(6, '0');
|
||||||
|
if (set.has(str)) {
|
||||||
|
return GiftPackModel.randomGiftNos(set);
|
||||||
|
} else {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
GiftPackModel.nextBatchNo = async () =>{
|
||||||
|
const record = await GiftPackModel.find({deleted: false}).limit(1).sort({batch_no: -1});
|
||||||
|
if (record.length > 0) {
|
||||||
|
return parseInt(record[0].batch_no) + 1;
|
||||||
|
} else {
|
||||||
|
return 1001;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
GiftPackModel.parseReq = async (req, record) => {
|
||||||
|
const body = req.body;
|
||||||
|
if (!record) {
|
||||||
|
record = new GiftPackModel({
|
||||||
|
createdBy: req.user.id,
|
||||||
|
});
|
||||||
|
record.gift_no_count = body.gift_count;
|
||||||
|
}
|
||||||
|
record.name = body.name;
|
||||||
|
record.game_id = body.game_id;
|
||||||
|
record.gift = body.gift;
|
||||||
|
if (body.begin_time) {
|
||||||
|
record.begin_time = moment(body.begin_time, 'YYYY-MM-DD HH:mm').toDate();
|
||||||
|
} else {
|
||||||
|
record.begin_time = null;
|
||||||
|
}
|
||||||
|
if (body.end_time) {
|
||||||
|
record.end_time = moment(body.end_time, 'YYYY-MM-DD HH:mm').toDate();
|
||||||
|
} else {
|
||||||
|
record.end_time = null;
|
||||||
|
}
|
||||||
|
record.vip = body.vip;
|
||||||
|
record.svr_id = body.svr_id;
|
||||||
|
record.svr_name = body.svr_name;
|
||||||
|
record.platforms = body.platforms;
|
||||||
|
record.type = body.type;
|
||||||
|
record.status = body.status;
|
||||||
|
return record.save();
|
||||||
|
};
|
||||||
|
|
||||||
|
GiftPackModel.parseQueryOpt = async (req) => {
|
||||||
|
let opt = {};
|
||||||
|
const query = req.query;
|
||||||
|
const keyStr = query.keyStr;
|
||||||
|
const status = parseInt(query.status);
|
||||||
|
const type = query.type;
|
||||||
|
const platform = query.platform;
|
||||||
|
let timeBegin = query.timeBegin;
|
||||||
|
let timeEnd = query.timeEnd;
|
||||||
|
const order = query.order;
|
||||||
|
const gameId = query.gameId;
|
||||||
|
const sort = query.sort ? query.sort : 'createdAt';
|
||||||
|
const sortObj = {};
|
||||||
|
const vip = query.vip;
|
||||||
|
sortObj[sort] = order === 'asc' ? 1 : -1;
|
||||||
|
if (keyStr) {
|
||||||
|
const orArr = [{name: {$regex: keyStr, $options: 'i'}}];
|
||||||
|
try {
|
||||||
|
const gamesArr = [
|
||||||
|
{game_name: {$regex: keyStr, $options: 'i'}},
|
||||||
|
{game_name_en: {$regex: keyStr, $options: 'i'}},
|
||||||
|
];
|
||||||
|
const games = await Games.find({$or: gamesArr});
|
||||||
|
const gameIds = games.map((o) => o.id);
|
||||||
|
if (gameIds.length > 0) orArr.push({game_id: {$in: gameIds}});
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const items = await Gift.find({name: {$regex: keyStr, $options: 'i'}});
|
||||||
|
const itemIds = items.map((o) => o.id);
|
||||||
|
if (itemIds.length > 0) orArr.push({gift: {$in: itemIds}});
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
opt = {$or: orArr};
|
||||||
|
}
|
||||||
|
if (status > -999) {
|
||||||
|
opt.status = status;
|
||||||
|
}
|
||||||
|
if (type !== 'all') {
|
||||||
|
opt.type = type;
|
||||||
|
}
|
||||||
|
if (platform !== 'all') {
|
||||||
|
opt.platforms = {$in: [platform]};
|
||||||
|
}
|
||||||
|
if (vip > -1) {
|
||||||
|
// opt.vip = {$lte: vip};
|
||||||
|
opt.vip = vip;
|
||||||
|
}
|
||||||
|
if (gameId) {
|
||||||
|
opt.game_id = gameId;
|
||||||
|
}
|
||||||
|
if (timeBegin && !timeEnd) {
|
||||||
|
timeBegin = moment(timeBegin, 'YYYY-MM-DD').startOf('day').format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
opt.end_time = {$gte: timeBegin};
|
||||||
|
} else if (timeBegin && timeEnd) {
|
||||||
|
timeBegin = moment(timeBegin, 'YYYY-MM-DD').startOf('day').format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
timeEnd = moment(timeBegin, 'YYYY-MM-DD').endOf('day').format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
opt['$or'] = [{end_time: {$gte: timeBegin}}, {begin_time: {$lte: timeEnd}}];
|
||||||
|
} else if (!timeBegin && timeEnd) {
|
||||||
|
timeEnd = moment(timeBegin, 'YYYY-MM-DD').endOf('day').format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
opt.begin_time = {$lte: timeEnd};
|
||||||
|
}
|
||||||
|
opt.deleted = false;
|
||||||
|
return {opt, sortObj};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GiftPackModel;
|
||||||
|
|
58
src/models/snoopy/Puzzle.js
Normal file
58
src/models/snoopy/Puzzle.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
'use strict';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 竞猜题库
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Puzzle = new mongoose.Schema({
|
||||||
|
// 类型, 1: 图片, 2: 音频, 3: 视频, 0: 纯文本
|
||||||
|
attachtype: {type: Number},
|
||||||
|
// 图片或者音频的url地址
|
||||||
|
attach: {type: String},
|
||||||
|
// 问题
|
||||||
|
question: {type: String},
|
||||||
|
// 答案扩展字符
|
||||||
|
option: {type: String},
|
||||||
|
// 答案
|
||||||
|
answer: {type: String},
|
||||||
|
// 扩展信息, 在猜菜名中, 该字段为菜系
|
||||||
|
extra: {type: String},
|
||||||
|
// 提示信息
|
||||||
|
tip: {type: String},
|
||||||
|
// 题目分类, food: 菜
|
||||||
|
type: {type: String},
|
||||||
|
// 备注
|
||||||
|
comment: {type: String},
|
||||||
|
createdBy: {type: String},
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
deletedBy: {type: String},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
}, {
|
||||||
|
collection: 'puzzle',
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnSnoopy();
|
||||||
|
const PuzzleModel = conn.model('Puzzle', Puzzle);
|
||||||
|
|
||||||
|
PuzzleModel.parseReq = (req, record) => {
|
||||||
|
if (!record) {
|
||||||
|
record = new PuzzleModel({
|
||||||
|
createdBy: req.user.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const body = req.body;
|
||||||
|
record.attachtype = body.attachtype;
|
||||||
|
record.attach = body.attach;
|
||||||
|
record.option = body.option;
|
||||||
|
record.question = body.question;
|
||||||
|
record.answer = body.answer;
|
||||||
|
record.extra = body.extra;
|
||||||
|
record.tip = body.tip;
|
||||||
|
record.type = body.type;
|
||||||
|
return record.save();
|
||||||
|
};
|
||||||
|
export default PuzzleModel;
|
||||||
|
|
82
src/models/snoopy/PuzzleLevel.js
Normal file
82
src/models/snoopy/PuzzleLevel.js
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
'use strict';
|
||||||
|
import mongoose from 'mongoose';
|
||||||
|
import dbUtil from '../../utils/db.util';
|
||||||
|
import Puzzle from './Puzzle';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 竞猜游戏关卡
|
||||||
|
*/
|
||||||
|
|
||||||
|
const PuzzleLevel = new mongoose.Schema({
|
||||||
|
game_id: {type: String, ref: 'GameInfo'},
|
||||||
|
levels: [
|
||||||
|
{
|
||||||
|
_id: false,
|
||||||
|
name: {type: String},
|
||||||
|
index: {type: Number},
|
||||||
|
sub_levels: [
|
||||||
|
{
|
||||||
|
_id: false,
|
||||||
|
name: {type: String},
|
||||||
|
index: {type: Number},
|
||||||
|
subjects: [{type: String, ref: 'Puzzle'}],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
cdn_base: {type: String},
|
||||||
|
createdBy: {type: String},
|
||||||
|
deleted: {type: Boolean, default: false},
|
||||||
|
deletedBy: {type: String},
|
||||||
|
delete_time: {type: Date},
|
||||||
|
}, {
|
||||||
|
collection: 'puzzle_level',
|
||||||
|
usePushEach: true,
|
||||||
|
timestamps: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const conn = dbUtil.getConnSnoopy();
|
||||||
|
const PuzzleLevelModel = conn.model('PuzzleLevel', PuzzleLevel);
|
||||||
|
|
||||||
|
PuzzleLevelModel.parseReq = (req, record) => {
|
||||||
|
if (!record) {
|
||||||
|
record = new PuzzleLevelModel({
|
||||||
|
createdBy: req.user.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return record.save();
|
||||||
|
};
|
||||||
|
|
||||||
|
PuzzleLevelModel.findByGame = (gameId) => {
|
||||||
|
return PuzzleLevelModel.findOne({game_id: gameId, deleted: false});
|
||||||
|
};
|
||||||
|
|
||||||
|
PuzzleLevelModel.generateLevelObj = (level) => {
|
||||||
|
const puzzles = [];
|
||||||
|
for (const subLevel of level.sub_levels) {
|
||||||
|
puzzles.push(...subLevel.subjects);
|
||||||
|
}
|
||||||
|
return Puzzle.find({_id: {$in: puzzles}}).select({attachtype: 1, attach: 1,
|
||||||
|
question: 1, option: 1, answer: 1, extra: 1, tip: 1})
|
||||||
|
.then((topics) => {
|
||||||
|
const topicMap = new Map();
|
||||||
|
for (const topic of topics) {
|
||||||
|
topicMap.set(topic.id, topic);
|
||||||
|
}
|
||||||
|
const resultArr = [];
|
||||||
|
for (const subLevel of level.sub_levels) {
|
||||||
|
const subjects = [];
|
||||||
|
for (const subject of subLevel.subjects) {
|
||||||
|
if (topicMap.has(subject)) {
|
||||||
|
const obj = topicMap.get(subject).toJSON();
|
||||||
|
delete obj['_id'];
|
||||||
|
subjects.push(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resultArr.push(subjects);
|
||||||
|
}
|
||||||
|
return resultArr;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export default PuzzleLevelModel;
|
||||||
|
|
11
src/router/index.js
Normal file
11
src/router/index.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
import {Router} from 'express';
|
||||||
|
|
||||||
|
import commonRouter from './../controllers/common'
|
||||||
|
|
||||||
|
const router = new Router();
|
||||||
|
|
||||||
|
router.use('/common', commonRouter);
|
||||||
|
|
||||||
|
|
||||||
|
export default router
|
81
src/schedule/weappmsg.schedule.js
Normal file
81
src/schedule/weappmsg.schedule.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
'use strict';
|
||||||
|
import schedule from 'node-schedule';
|
||||||
|
import logger from '../utils/logger';
|
||||||
|
import wechatUtil from '../utils/wechat.utils';
|
||||||
|
import config from '../../config/config';
|
||||||
|
import WeappFormID from '../models/beagle/WeappFormID';
|
||||||
|
import TemplateMsgRecord from '../models/admin/TemplateMsgRecord';
|
||||||
|
|
||||||
|
|
||||||
|
const msgData = {
|
||||||
|
'touser': '',
|
||||||
|
'weapp_template_msg': {
|
||||||
|
'template_id': config.pay_weapp.message_template_id,
|
||||||
|
'page': '/pages/index',
|
||||||
|
'form_id': '',
|
||||||
|
'data': {
|
||||||
|
'keyword1': {
|
||||||
|
'value': config.pay_weapp.message_value1,
|
||||||
|
},
|
||||||
|
'keyword2': {
|
||||||
|
'value': config.pay_weapp.message_value2,
|
||||||
|
},
|
||||||
|
'keyword3': {
|
||||||
|
'value': config.pay_weapp.message_value3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendOneTemplateMsg = async function(openId, formId, accessToken) {
|
||||||
|
msgData.touser = openId;
|
||||||
|
msgData.weapp_template_msg.form_id = formId;
|
||||||
|
try {
|
||||||
|
await wechatUtil.sendMsgToUser(accessToken, msgData);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export default {
|
||||||
|
async sendAll() {
|
||||||
|
logger.info('开始发送小程序模版消息');
|
||||||
|
try {
|
||||||
|
await WeappFormID.updateExpireTime();
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err);
|
||||||
|
}
|
||||||
|
const accessToken = await wechatUtil.getPayAccessToken();
|
||||||
|
const records = await WeappFormID.getUserRecordsDay(config.pay_weapp.app_id);
|
||||||
|
const openIds = records.map((o) => o.open_id);
|
||||||
|
if (records && records.length > 0) {
|
||||||
|
for (const record of records) {
|
||||||
|
try {
|
||||||
|
await sendOneTemplateMsg(record.open_id, record.form_id, accessToken);
|
||||||
|
record.status = 1;
|
||||||
|
record.send_time = new Date();
|
||||||
|
await record.save();
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const sendRecord = new TemplateMsgRecord({
|
||||||
|
app_id: config.pay_weapp.app_id,
|
||||||
|
open_ids: openIds,
|
||||||
|
});
|
||||||
|
await sendRecord.save();
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/*
|
||||||
|
* 设定每天凌晨8:30:30秒把刷新任务加入队列
|
||||||
|
* */
|
||||||
|
scheduleSendAll() {
|
||||||
|
logger.info('已添加发送小程序模版消息的定时任务');
|
||||||
|
schedule.scheduleJob(config.pay_weapp.schedule_send_time, async () => {
|
||||||
|
this.sendAll();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
109
src/utils/admin_keeper.js
Normal file
109
src/utils/admin_keeper.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import logger from './logger';
|
||||||
|
import Promise from 'bluebird';
|
||||||
|
import {AdminAction, AdminRole} from '../models/admin/Admin';
|
||||||
|
|
||||||
|
const pathPermissions = new Map();
|
||||||
|
const basicActions = new Set();
|
||||||
|
const pathRoles = new Map();
|
||||||
|
const roles = new Map();
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
AdminAction.find({deleted: false}).select('_id paths'),
|
||||||
|
AdminRole.find({deleted: false})
|
||||||
|
.select('_id permissions')
|
||||||
|
.populate('permissions', '_id paths'),
|
||||||
|
]).then(([actions, rs]) => {
|
||||||
|
for (const action of actions) {
|
||||||
|
for (const path of action.paths) {
|
||||||
|
const uniPath = path.method + ':' + path.path;
|
||||||
|
if (action._id === '_basic_actions') {
|
||||||
|
basicActions.add(uniPath);
|
||||||
|
} else {
|
||||||
|
if (!pathPermissions.has(uniPath)) {
|
||||||
|
pathPermissions.set(uniPath, new Set());
|
||||||
|
}
|
||||||
|
pathPermissions.get(uniPath).add(action._id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const role of rs) {
|
||||||
|
if (!roles.has(role.id)) {
|
||||||
|
roles.set(role.id, new Set());
|
||||||
|
}
|
||||||
|
for (const action of role.permissions) {
|
||||||
|
roles.get(role.id).add(action.id);
|
||||||
|
for (const path of action.paths) {
|
||||||
|
const uniPath = path.method + ':' + path.path;
|
||||||
|
if (!pathRoles.has(uniPath)) {
|
||||||
|
pathRoles.set(uniPath, new Set());
|
||||||
|
}
|
||||||
|
pathRoles.get(uniPath).add(role._id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info('load permissions success, NEED REFRESH');
|
||||||
|
}).catch((err) => {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasPermission = function(user, path) {
|
||||||
|
let yes = false;
|
||||||
|
if (pathPermissions.has(path)) {
|
||||||
|
const allowedPermissions = pathPermissions.get(path);
|
||||||
|
for (const permission of user.permissions) {
|
||||||
|
if (!yes && allowedPermissions.has(permission)) {
|
||||||
|
yes = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!yes && pathRoles.has(path)) {
|
||||||
|
const allowedRoles = pathRoles.get(path);
|
||||||
|
for (const role of user.roles) {
|
||||||
|
if (!yes && allowedRoles.has(role)) {
|
||||||
|
yes = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return yes;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
gatekeeper: function(req, res, next) {
|
||||||
|
const path = req.method + ':' + req.baseUrl + req.path;
|
||||||
|
if (req.isAuthenticated()) {
|
||||||
|
const params = req.method === 'GET' ? req.query : req.body;
|
||||||
|
const ip = req.headers['x-forwarded-for'];
|
||||||
|
logger.info({user: req.user.username, path: path, from: ip, params: params});
|
||||||
|
let mayPass = false;
|
||||||
|
if (basicActions.has(path)) {
|
||||||
|
mayPass = true;
|
||||||
|
} else {
|
||||||
|
mayPass = hasPermission(req.user, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mayPass) {
|
||||||
|
res.locals.lastLogin = req.session.lastLogin;
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
const accept = req.headers.accept || '';
|
||||||
|
if (~accept.indexOf('html')) {
|
||||||
|
const err = new Error('您没有权限访问该功能');
|
||||||
|
err.status = 403;
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
res.status(403).send('您没有权限访问该功能');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (path !== '/logout') {
|
||||||
|
req.session.path_before_login = req.originalUrl;
|
||||||
|
}
|
||||||
|
res.redirect('/login.html');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
75
src/utils/captcha.js
Normal file
75
src/utils/captcha.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
'use strict';
|
||||||
|
import svgCaptcha from 'svg-captcha';
|
||||||
|
|
||||||
|
const captchaUtil = {
|
||||||
|
generate_captcha: function() {
|
||||||
|
// const captcha = svgCaptcha.create({
|
||||||
|
// size: 4,
|
||||||
|
// ignoreChars: '0o1i',
|
||||||
|
// });
|
||||||
|
const captcha = svgCaptcha.createMathExpr();
|
||||||
|
return captcha;
|
||||||
|
},
|
||||||
|
|
||||||
|
validate_captcha: function(req, captcha, storedCaptcha, stamp) {
|
||||||
|
if (!storedCaptcha || !captcha) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!stamp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Date.now() - stamp > 600000) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return storedCaptcha === captcha.toUpperCase();
|
||||||
|
},
|
||||||
|
|
||||||
|
validate_register_captcha: function(req, captcha) {
|
||||||
|
return captchaUtil.validate_captcha(req, captcha, req.session.captcha_register_mobile,
|
||||||
|
req.session.captcha_register_mobile_stamp);
|
||||||
|
},
|
||||||
|
|
||||||
|
validate_login_captcha: function(req, captcha) {
|
||||||
|
return captchaUtil.validate_captcha(req, captcha, req.session.captcha_login_mobile,
|
||||||
|
req.session.captcha_login_mobile_stamp);
|
||||||
|
},
|
||||||
|
|
||||||
|
validate_findpwd_captcha: function(req, captcha) {
|
||||||
|
return captchaUtil.validate_captcha(req, captcha, req.session.captcha_findpwd_mobile,
|
||||||
|
req.session.captcha_findpwd_mobile_stamp);
|
||||||
|
},
|
||||||
|
|
||||||
|
validate_admin_login_captcha: function(req, captcha) {
|
||||||
|
return captchaUtil.validate_captcha(req, captcha, req.session.captcha_admin_login,
|
||||||
|
req.session.captcha_admin_login_stamp);
|
||||||
|
},
|
||||||
|
|
||||||
|
validate_mobile_code: function(req, stamp, storedMobile, storedCode) {
|
||||||
|
const mobile = req.body.mobile;
|
||||||
|
if (!mobile) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const mobileCode = req.body.mobile_code;
|
||||||
|
if (!mobileCode) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!stamp) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
if (Date.now() - stamp > 600000) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
if (!storedMobile) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!storedCode) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (mobile !== storedMobile || mobileCode !== storedCode) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = captchaUtil;
|
99
src/utils/cdn.utils.js
Normal file
99
src/utils/cdn.utils.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import config from '../../config/config';
|
||||||
|
import request from 'request';
|
||||||
|
import Promise from 'bluebird';
|
||||||
|
import stringUtil from './string.utils';
|
||||||
|
import COS from 'cos-nodejs-sdk-v5';
|
||||||
|
|
||||||
|
const generateNonce = function() {
|
||||||
|
return stringUtil.randomNum(10000, 99999);
|
||||||
|
};
|
||||||
|
const generateSign = function(method, data) {
|
||||||
|
let str = `${method}cdn.api.qcloud.com/v2/index.php?`;
|
||||||
|
let i = 0;
|
||||||
|
for (const key in data) {
|
||||||
|
if ({}.hasOwnProperty.call(data, key)) {
|
||||||
|
if (i ++ > 0) str += '&';
|
||||||
|
str += `${key}=${data[key]}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stringUtil.sha1keyBase64(str, config.cos_cdn.SecretKey);
|
||||||
|
};
|
||||||
|
const cosCDN = new COS({
|
||||||
|
SecretId: config.cos_cdn.SecretId,
|
||||||
|
SecretKey: config.cos_cdn.SecretKey,
|
||||||
|
});
|
||||||
|
export default {
|
||||||
|
|
||||||
|
refreshDir(url) {
|
||||||
|
const now = Math.round(new Date()/1000);
|
||||||
|
const data = {
|
||||||
|
'Action': 'RefreshCdnDir',
|
||||||
|
'Nonce': generateNonce(),
|
||||||
|
'SecretId': config.cos_cdn.SecretId,
|
||||||
|
'Timestamp': now,
|
||||||
|
'dirs.0': url,
|
||||||
|
};
|
||||||
|
data.Signature = generateSign('POST', data);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const link = 'https://cdn.api.qcloud.com/v2/index.php';
|
||||||
|
const options = {method: 'POST',
|
||||||
|
url: link,
|
||||||
|
headers: {
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
form: data,
|
||||||
|
};
|
||||||
|
request(options, (err, response, body) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
resolve(JSON.parse(body));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
refreshOneUrl(url) {
|
||||||
|
const now = Math.round(new Date()/1000);
|
||||||
|
const data = {
|
||||||
|
'Action': 'RefreshCdnUrl',
|
||||||
|
'Nonce': generateNonce(),
|
||||||
|
'SecretId': config.cos_cdn.SecretId,
|
||||||
|
'Timestamp': now,
|
||||||
|
'urls.0': url,
|
||||||
|
};
|
||||||
|
data.Signature = generateSign('POST', data);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const link = 'https://cdn.api.qcloud.com/v2/index.php';
|
||||||
|
const options = {method: 'POST',
|
||||||
|
url: link,
|
||||||
|
headers: {
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
form: data,
|
||||||
|
};
|
||||||
|
request(options, (err, response, body) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
resolve(JSON.parse(body));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
uploadToCDN(fileName, path) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
cosCDN.sliceUploadFile({
|
||||||
|
Bucket: 'client-1256832210',
|
||||||
|
Region: 'ap-beijing',
|
||||||
|
Key: fileName,
|
||||||
|
FilePath: path,
|
||||||
|
}, function(err, data) {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
51
src/utils/db.util.js
Normal file
51
src/utils/db.util.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import mongoose from 'mongoose';
|
||||||
|
import Promise from 'bluebird';
|
||||||
|
import config from '../../config/config';
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
validatePost: function(req, validate) {
|
||||||
|
for (const rule of validate.rules) {
|
||||||
|
if (rule[1]) {
|
||||||
|
req.checkBody(rule[0], rule[2]).notEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return req.validationErrors();
|
||||||
|
},
|
||||||
|
|
||||||
|
leanWithId: function(docs) {
|
||||||
|
for (const doc of docs) {
|
||||||
|
doc.id = String(doc._id);
|
||||||
|
}
|
||||||
|
return docs;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 根据db名,获取连接
|
||||||
|
* @param {string} dbName
|
||||||
|
* @return {Object} dbConnection
|
||||||
|
* */
|
||||||
|
getConnection: function(dbName) {
|
||||||
|
const url = config.db_admin.slice(0, config.db_admin.lastIndexOf('/') + 1) + dbName;
|
||||||
|
return mongoose.createConnection(url, {promiseLibrary: Promise, useNewUrlParser: true});
|
||||||
|
},
|
||||||
|
getConnAdmin: function() {
|
||||||
|
return mongoose.createConnection(config.db_admin, {promiseLibrary: Promise, useNewUrlParser: true});
|
||||||
|
},
|
||||||
|
|
||||||
|
getConnSnoopy: function() {
|
||||||
|
return mongoose.createConnection(config.db_snoopy, {promiseLibrary: Promise, useNewUrlParser: true});
|
||||||
|
},
|
||||||
|
|
||||||
|
getConnDalmatian: function() {
|
||||||
|
return mongoose.createConnection(config.db_dalmatian, {promiseLibrary: Promise, useNewUrlParser: true});
|
||||||
|
},
|
||||||
|
|
||||||
|
getConnBeagle: function() {
|
||||||
|
return mongoose.createConnection(config.db_beagle, {promiseLibrary: Promise, useNewUrlParser: true});
|
||||||
|
},
|
||||||
|
|
||||||
|
getConnGhost: function() {
|
||||||
|
return mongoose.createConnection(config.db_ghost, {promiseLibrary: Promise, useNewUrlParser: true});
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
58
src/utils/error-utils.js
Normal file
58
src/utils/error-utils.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
format_alert: function(msg) {
|
||||||
|
if (msg) {
|
||||||
|
if (typeof msg === 'string') {
|
||||||
|
return msg;
|
||||||
|
} else if (typeof msg[Symbol.iterator] === 'function') {
|
||||||
|
let msgs = '<ul>';
|
||||||
|
_.each(msg, function(m) {
|
||||||
|
if (typeof m === 'string') {
|
||||||
|
msgs += '<li>' + m + '</li>';
|
||||||
|
} else if (_.has(m, 'msg')) {
|
||||||
|
msgs += '<li>' + m.msg + '</li>';
|
||||||
|
} else if (_.has(m, 'message')) {
|
||||||
|
msgs += '<li>' + m.message + '</li>';
|
||||||
|
} else {
|
||||||
|
msgs += '<li>' + JSON.stringify(m) + '</li>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
msgs += '</ul>';
|
||||||
|
return msgs;
|
||||||
|
} else {
|
||||||
|
return JSON.stringify(msg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
format_notify: function(msg) {
|
||||||
|
if (msg) {
|
||||||
|
if (typeof msg === 'string') {
|
||||||
|
return [msg];
|
||||||
|
} else if (typeof msg[Symbol.iterator] === 'function') {
|
||||||
|
const msgs = [];
|
||||||
|
_.each(msg, function(m) {
|
||||||
|
if (typeof m === 'string') {
|
||||||
|
msgs.push(m);
|
||||||
|
} else if (_.has(m, 'msg')) {
|
||||||
|
msgs.push(m.msg);
|
||||||
|
} else if (_.has(m, 'message')) {
|
||||||
|
msgs.push(m.message);
|
||||||
|
} else {
|
||||||
|
msgs.push(JSON.stringify(m));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return msgs;
|
||||||
|
} else {
|
||||||
|
return [JSON.stringify(msg)];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
53
src/utils/express-utils.js
Normal file
53
src/utils/express-utils.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import errorUtils from './error-utils';
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
return function(req, res, next) {
|
||||||
|
req.alert = function(msg) {
|
||||||
|
const m = errorUtils.format_alert(msg);
|
||||||
|
if (m) {
|
||||||
|
req.flash('alert', m);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} type one of ['notify', 'success', 'warning', 'error']
|
||||||
|
* @param {string} msg
|
||||||
|
*/
|
||||||
|
req.notify = function(type, msg) {
|
||||||
|
const messages = errorUtils.format_notify(msg);
|
||||||
|
if (messages) {
|
||||||
|
const prefix = 'alertify.' + (type ? type : 'notify') + '(\'';
|
||||||
|
const postfix = '\');';
|
||||||
|
for (const m of messages) {
|
||||||
|
req.flash('notify', prefix + m + postfix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
res.renderWithValidate = function(template, obj, validate) {
|
||||||
|
const objWithValidate = obj ? obj : {};
|
||||||
|
objWithValidate.validate = validate;
|
||||||
|
res.render(template, objWithValidate);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 接口成功返回数据的方法, 统一返回errcode 和 errmsg
|
||||||
|
* @param {Object} data 要返回的数据
|
||||||
|
* */
|
||||||
|
res.successJson = function(data) {
|
||||||
|
data.errcode = 0;
|
||||||
|
data.errmsg = '';
|
||||||
|
res.json(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} errcode 错误代码
|
||||||
|
* @param {string} errmsg 错误消息
|
||||||
|
* */
|
||||||
|
res.errorJson = function(errcode, errmsg) {
|
||||||
|
res.json({errcode, errmsg});
|
||||||
|
};
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
};
|
131
src/utils/file.utils.js
Normal file
131
src/utils/file.utils.js
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
'use strict';
|
||||||
|
import fs from 'fs';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import mime from 'mime-types';
|
||||||
|
import dateformat from 'dateformat';
|
||||||
|
import config from '../../config/config';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} str 待hash的string
|
||||||
|
* @return {String}
|
||||||
|
* */
|
||||||
|
function cryptPwd(str) {
|
||||||
|
const md5 = crypto.createHash('md5');
|
||||||
|
return md5.update(str).digest('hex');
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
getFileMimeTypeWithCode(typeCode) {
|
||||||
|
let filetype = '';
|
||||||
|
let mimetype;
|
||||||
|
switch (typeCode) {
|
||||||
|
case 'ffd8ffe1':
|
||||||
|
filetype = 'jpg';
|
||||||
|
mimetype = ['image/jpeg', 'image/pjpeg'];
|
||||||
|
break;
|
||||||
|
case '47494638':
|
||||||
|
filetype = 'gif';
|
||||||
|
mimetype = 'image/gif';
|
||||||
|
break;
|
||||||
|
case '89504e47':
|
||||||
|
filetype = 'png';
|
||||||
|
mimetype = ['image/png', 'image/x-png'];
|
||||||
|
break;
|
||||||
|
case '504b34':
|
||||||
|
filetype = 'zip';
|
||||||
|
mimetype = ['application/x-zip', 'application/zip', 'application/x-zip-compressed'];
|
||||||
|
break;
|
||||||
|
case '2f2aae5':
|
||||||
|
filetype = 'js';
|
||||||
|
mimetype = 'application/x-javascript';
|
||||||
|
break;
|
||||||
|
case '2f2ae585':
|
||||||
|
filetype = 'css';
|
||||||
|
mimetype = 'text/css';
|
||||||
|
break;
|
||||||
|
case '5b7bda':
|
||||||
|
filetype = 'json';
|
||||||
|
mimetype = ['application/json', 'text/json'];
|
||||||
|
break;
|
||||||
|
case '3c212d2d':
|
||||||
|
filetype = 'ejs';
|
||||||
|
mimetype = 'text/html';
|
||||||
|
break;
|
||||||
|
case '52494646':
|
||||||
|
filetype = 'webp';
|
||||||
|
mimetype = 'image/webp';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
filetype = 'unknown';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
fileType: filetype,
|
||||||
|
mimeType: mimetype,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
isImage(fileType) {
|
||||||
|
return fileType === 'jpg' || fileType === 'jpeg' || fileType === 'png' || fileType === 'gif' || fileType === 'webp';
|
||||||
|
},
|
||||||
|
getFileMimeType: function(filePath) {
|
||||||
|
const buffer = new Buffer(8);
|
||||||
|
const fd = fs.openSync(filePath, 'r');
|
||||||
|
fs.readSync(fd, buffer, 0, 8, 0);
|
||||||
|
const newBuf = buffer.slice(0, 4);
|
||||||
|
const head1 = newBuf[0].toString(16);
|
||||||
|
const head2 = newBuf[1].toString(16);
|
||||||
|
const head3 = newBuf[2].toString(16);
|
||||||
|
const head4 = newBuf[3].toString(16);
|
||||||
|
const typeCode = head1 + head2 + head3 + head4;
|
||||||
|
const result = this.getFileMimeTypeWithCode(typeCode);
|
||||||
|
fs.closeSync(fd);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
/* 获取头像路径
|
||||||
|
* wid: 微信id
|
||||||
|
* bid: 机器人id
|
||||||
|
* remote: 是否返回url, 否的话返回本地存储路径
|
||||||
|
* */
|
||||||
|
getAvatarUrl(wid, bid, remote) {
|
||||||
|
const widPath = cryptPwd(wid);
|
||||||
|
let localPath = this.getAvatarPath(bid, remote);
|
||||||
|
localPath += widPath + '.jpg';
|
||||||
|
return localPath;
|
||||||
|
},
|
||||||
|
getAvatarPath(bid, remote) {
|
||||||
|
let localPath = config.upload_to + '/';
|
||||||
|
if (remote) {
|
||||||
|
localPath = config.upload_prefix + '/';
|
||||||
|
}
|
||||||
|
localPath += 'b/'+ bid + '/avatar/';
|
||||||
|
return localPath;
|
||||||
|
},
|
||||||
|
/* 获取图片本地保存路径*/
|
||||||
|
getFilePath() {
|
||||||
|
let localPath = config.upload_to + '/g/';
|
||||||
|
localPath += dateformat('yyyy') + '/';
|
||||||
|
localPath += dateformat('mm') + '/';
|
||||||
|
localPath += dateformat('dd') + '/';
|
||||||
|
return localPath;
|
||||||
|
},
|
||||||
|
/* 获取图片等的本地访问url*/
|
||||||
|
getFileUrl(type) {
|
||||||
|
let localPath = config.upload_prefix + '/g/';
|
||||||
|
localPath += dateformat('yyyy') + '/';
|
||||||
|
localPath += dateformat('mm') + '/';
|
||||||
|
localPath += dateformat('dd') + '/';
|
||||||
|
return localPath;
|
||||||
|
},
|
||||||
|
extension(file) {
|
||||||
|
let ext = mime.extension(file.mimetype);
|
||||||
|
if (ext) {
|
||||||
|
ext = '.' + ext;
|
||||||
|
} else {
|
||||||
|
ext = path.extname(file.originalname);
|
||||||
|
ext = ext ? ext.toLowerCase() : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return ext;
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
299
src/utils/gamesvr.utils.js
Normal file
299
src/utils/gamesvr.utils.js
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
import request from "request";
|
||||||
|
import Promise from "bluebird";
|
||||||
|
import logger from "./logger";
|
||||||
|
|
||||||
|
const requestData = function(options) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
request(options, (err, response, body) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
if (response && response.statusCode === 200) {
|
||||||
|
const data = JSON.parse(body);
|
||||||
|
if (data.errcode) {
|
||||||
|
return reject(new Error(data.errmsg));
|
||||||
|
}
|
||||||
|
resolve(data);
|
||||||
|
} else {
|
||||||
|
logger.error(
|
||||||
|
options,
|
||||||
|
`server response errorCode: ${response && response.statusCode}`
|
||||||
|
);
|
||||||
|
reject(
|
||||||
|
new Error(
|
||||||
|
`server response errorCode: ${response && response.statusCode}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export default {
|
||||||
|
queryScrollList(svr) {
|
||||||
|
const qs = {
|
||||||
|
c: "RollMsg",
|
||||||
|
a: "getMsgList"
|
||||||
|
};
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: qs,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
updateScroll(svr, data) {
|
||||||
|
data.c = "RollMsg";
|
||||||
|
data.a = "updateMsg";
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: data,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
addScroll(svr, data) {
|
||||||
|
data.c = "RollMsg";
|
||||||
|
data.a = "addMsg";
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: data,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
deleteScroll(svr, msgid) {
|
||||||
|
const qs = {
|
||||||
|
c: "RollMsg",
|
||||||
|
a: "removeMsg",
|
||||||
|
msgid: msgid
|
||||||
|
};
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: qs,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
/* begin of mail*/
|
||||||
|
queryMailList(svr) {
|
||||||
|
const qs = {
|
||||||
|
c: "Mail",
|
||||||
|
a: "getMailList"
|
||||||
|
};
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: qs,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
updateMail(svr, data) {
|
||||||
|
data.c = "Mail";
|
||||||
|
data.a = "updateMail";
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: data,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
addMail(svr, data) {
|
||||||
|
data.c = "Mail";
|
||||||
|
data.a = "addMail";
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: data,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
deleteMail(svr, mailid) {
|
||||||
|
const qs = {
|
||||||
|
c: "Mail",
|
||||||
|
a: "deleteMail",
|
||||||
|
mailid: mailid
|
||||||
|
};
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: qs,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
getItemNames(svr, itemIdArr) {
|
||||||
|
const itemIdStr = itemIdArr.join(",");
|
||||||
|
const qs = {
|
||||||
|
c: "Item",
|
||||||
|
a: "getItemsInfo",
|
||||||
|
item_ids: itemIdStr
|
||||||
|
};
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: qs,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
searchItem(svr, { itemId, itemName, start, limit }) {
|
||||||
|
const qs = {
|
||||||
|
c: "Item",
|
||||||
|
a: "searchItem",
|
||||||
|
start: start,
|
||||||
|
limit: limit
|
||||||
|
};
|
||||||
|
if (itemId) qs.item_id = itemId;
|
||||||
|
if (itemName) qs.item_name = itemName;
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: qs,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
/* end of mail*/
|
||||||
|
/* begin of activity*/
|
||||||
|
queryActivityList(svr) {
|
||||||
|
const qs = {
|
||||||
|
c: "Activity",
|
||||||
|
a: "getActivityList"
|
||||||
|
};
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: qs,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
updateActivity(svr, data) {
|
||||||
|
data.c = "Activity";
|
||||||
|
data.a = "updateActivity";
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: data,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
/* end of activity*/
|
||||||
|
|
||||||
|
/* begin of recharge*/
|
||||||
|
queryRechargeList(svr, data) {
|
||||||
|
data.c = "GMTool";
|
||||||
|
data.a = "execSql";
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: data,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
addRecharge(svr, data) {
|
||||||
|
data.c = "GMTool";
|
||||||
|
data.a = "addVirtualOrder";
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: data,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
|
||||||
|
/* end of recharge*/
|
||||||
|
|
||||||
|
/* begin of users*/
|
||||||
|
queryUsersList(svr, data) {
|
||||||
|
data.c = "User";
|
||||||
|
data.a = "searchUser";
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: data,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
updateUser(svr, data) {
|
||||||
|
data.c = "Item";
|
||||||
|
data.a = "searchUser";
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: data,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
// deleteUser(svr, accountid) {
|
||||||
|
// const qs = {
|
||||||
|
// accountid: accountid
|
||||||
|
// };
|
||||||
|
// const options = {
|
||||||
|
// url: svr,
|
||||||
|
// qs: qs,
|
||||||
|
// headers: { "cache-control": "no-cache" }
|
||||||
|
// };
|
||||||
|
// return requestData(options);
|
||||||
|
// },
|
||||||
|
shutupUser(svr, data) {
|
||||||
|
data.c = "User";
|
||||||
|
data.a = "forbidSpeak";
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: data,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
forbidUser(svr, data) {
|
||||||
|
data.c = "User";
|
||||||
|
data.a = "forbidAccount";
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: data,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
disforbidUser(svr, data) {
|
||||||
|
data.c = "User";
|
||||||
|
data.a = "disforbidAccount";
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: data,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
disforbidSpeak(svr, data) {
|
||||||
|
data.c = "User";
|
||||||
|
data.a = "forbidSpeak";
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: data,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
queryForbid(svr, data) {
|
||||||
|
data.c = "User";
|
||||||
|
data.a = "searchForbidAccount";
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: data,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
queryShutup(svr, data) {
|
||||||
|
data.c = "User";
|
||||||
|
data.a = "searchForbidSpeak";
|
||||||
|
const options = {
|
||||||
|
url: svr,
|
||||||
|
qs: data,
|
||||||
|
headers: { "cache-control": "no-cache" }
|
||||||
|
};
|
||||||
|
return requestData(options);
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
/* end of users*/
|
||||||
|
|
||||||
|
};
|
21
src/utils/gm-logs.js
Normal file
21
src/utils/gm-logs.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export default {
|
||||||
|
type: {
|
||||||
|
servers: "服务器管理",
|
||||||
|
announces: "公告",
|
||||||
|
scroll_texts: "跑马灯",
|
||||||
|
mails: "邮件",
|
||||||
|
activity: "活动",
|
||||||
|
list: "兑换码",
|
||||||
|
gift_list: "礼包",
|
||||||
|
recharge_record: "充值记录",
|
||||||
|
recharge_request: "充值请求",
|
||||||
|
users: "用户管理",
|
||||||
|
chat_logs: "聊天信息",
|
||||||
|
op_logs: "操作记录"
|
||||||
|
},
|
||||||
|
abstractType(path) {
|
||||||
|
const reg = /\/gm\/(.+).html/g;
|
||||||
|
const result = reg.exec(path);
|
||||||
|
return result ? result[1] : '';
|
||||||
|
}
|
||||||
|
};
|
122
src/utils/logger.js
Normal file
122
src/utils/logger.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
'use strict';
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
import FileStreamRotator from 'file-stream-rotator';
|
||||||
|
import bunyan from 'bunyan';
|
||||||
|
import config from '../../config/config';
|
||||||
|
import AdminLog from '../models/admin/AdminLog';
|
||||||
|
|
||||||
|
|
||||||
|
const env = process.env.NODE_ENV || 'development';
|
||||||
|
const isDev = env === 'development';
|
||||||
|
|
||||||
|
const logDir = config.logs_path;
|
||||||
|
fs.existsSync(logDir) || fs.mkdirSync(logDir);
|
||||||
|
|
||||||
|
let logger = null;
|
||||||
|
const createLogger = function(appName) {
|
||||||
|
appName = !appName ? config.app.name : appName;
|
||||||
|
const streams = [{
|
||||||
|
level: 'info',
|
||||||
|
stream: FileStreamRotator.getStream({
|
||||||
|
date_format: 'YYYYMMDD',
|
||||||
|
filename: `${logDir}/${appName}-%DATE%.log`,
|
||||||
|
frequency: 'daily',
|
||||||
|
verbose: false,
|
||||||
|
}),
|
||||||
|
}];
|
||||||
|
if (isDev) {
|
||||||
|
streams.push({
|
||||||
|
level: 'debug',
|
||||||
|
stream: process.stdout,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return bunyan.createLogger({
|
||||||
|
name: appName,
|
||||||
|
serializers: bunyan.stdSerializers,
|
||||||
|
streams: streams,
|
||||||
|
src: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export default {
|
||||||
|
info(obj, msg) {
|
||||||
|
if (!logger) {
|
||||||
|
logger = createLogger(global.app_name);
|
||||||
|
}
|
||||||
|
if (msg) {
|
||||||
|
logger.info(obj, msg);
|
||||||
|
} else {
|
||||||
|
logger.info(obj);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error(obj, msg) {
|
||||||
|
if (!logger) {
|
||||||
|
logger = createLogger(global.app_name);
|
||||||
|
}
|
||||||
|
if (msg) {
|
||||||
|
logger.error(obj, msg);
|
||||||
|
} else {
|
||||||
|
logger.error(obj);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
warn(obj, msg) {
|
||||||
|
if (!logger) {
|
||||||
|
logger = createLogger(global.app_name);
|
||||||
|
}
|
||||||
|
if (msg) {
|
||||||
|
logger.warn(obj, msg);
|
||||||
|
} else {
|
||||||
|
logger.warn(obj);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
debug(obj, msg) {
|
||||||
|
if (!logger) {
|
||||||
|
logger = createLogger(global.app_name);
|
||||||
|
}
|
||||||
|
if (msg) {
|
||||||
|
logger.debug(obj, msg);
|
||||||
|
} else {
|
||||||
|
logger.debug(obj);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trace(obj, msg) {
|
||||||
|
if (!logger) {
|
||||||
|
logger = createLogger(global.app_name);
|
||||||
|
}
|
||||||
|
if (msg) {
|
||||||
|
logger.trace(obj, msg);
|
||||||
|
} else {
|
||||||
|
logger.trace(obj);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fatal(obj, msg) {
|
||||||
|
if (!logger) {
|
||||||
|
logger = createLogger(global.app_name);
|
||||||
|
}
|
||||||
|
if (msg) {
|
||||||
|
logger.fatal(obj, msg);
|
||||||
|
} else {
|
||||||
|
logger.fatal(obj);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
db(req, logObj, name) {
|
||||||
|
const user = req.user;
|
||||||
|
const ip = req.headers['x-forwarded-for'];
|
||||||
|
const path = req.baseUrl + req.path;
|
||||||
|
const params = req.method === 'GET' ? req.query : req.body;
|
||||||
|
const dataObj = JSON.stringify(logObj) === '{}' ? params : logObj;
|
||||||
|
const obj = new AdminLog({
|
||||||
|
admin: user.id,
|
||||||
|
username: user.username,
|
||||||
|
path: path,
|
||||||
|
method: req.method,
|
||||||
|
params: dataObj,
|
||||||
|
referer: req.headers['referer'],
|
||||||
|
user_agent: req.headers['user-agent'],
|
||||||
|
ip: ip,
|
||||||
|
show_name: name,
|
||||||
|
});
|
||||||
|
obj.save().then(()=>{}).catch((err)=> {
|
||||||
|
logger.error(err);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
236
src/utils/string.utils.js
Normal file
236
src/utils/string.utils.js
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
'use strict';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import format from 'biguint-format';
|
||||||
|
|
||||||
|
const chnNumChar = {
|
||||||
|
零: 0,
|
||||||
|
一: 1,
|
||||||
|
二: 2,
|
||||||
|
三: 3,
|
||||||
|
四: 4,
|
||||||
|
五: 5,
|
||||||
|
六: 6,
|
||||||
|
七: 7,
|
||||||
|
八: 8,
|
||||||
|
九: 9,
|
||||||
|
};
|
||||||
|
const chnNameValue = {
|
||||||
|
十: {value: 10, secUnit: false},
|
||||||
|
百: {value: 100, secUnit: false},
|
||||||
|
千: {value: 1000, secUnit: false},
|
||||||
|
万: {value: 10000, secUnit: true},
|
||||||
|
亿: {value: 100000000, secUnit: true},
|
||||||
|
};
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 判断传入的值是否为true
|
||||||
|
* @param {string} obj 传入值为'true','TRUE',1,'1','on','ON','YES','yes'时,返回true,其他值均返回false
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
isTrue(obj) {
|
||||||
|
return obj === 'true' || obj === 'TRUE' || obj === 'on' || obj === 'ON' || obj === true || obj === 1
|
||||||
|
|| obj === '1' || obj === 'YES' || obj === 'yes';
|
||||||
|
},
|
||||||
|
isNull(obj) {
|
||||||
|
return !obj || obj === 'null' || obj === 'NULL' || obj === '' || obj === 'undefined';
|
||||||
|
},
|
||||||
|
isObjNull(obj) {
|
||||||
|
return !obj || JSON.stringify(obj) === '{}';
|
||||||
|
},
|
||||||
|
splitString(text, separator, length) {
|
||||||
|
const list = [];
|
||||||
|
let lastIndex = 0;
|
||||||
|
for (let i = 0; i < length - 1; i++) {
|
||||||
|
const j = text.indexOf(separator, lastIndex);
|
||||||
|
if (j === -1) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
list.push(text.slice(lastIndex, j));
|
||||||
|
lastIndex = j + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.push(text.slice(lastIndex, text.length));
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
md5(text) {
|
||||||
|
return crypto.createHash('md5').update(this.toBuffer(text)).digest('hex');
|
||||||
|
},
|
||||||
|
sha1(str) {
|
||||||
|
return crypto.createHash('sha1').update(str).digest('hex');
|
||||||
|
},
|
||||||
|
sha1keyBase64(str, key) {
|
||||||
|
return crypto.createHmac('sha1', key).update(str).digest('base64');
|
||||||
|
},
|
||||||
|
toBuffer(data) {
|
||||||
|
if (Buffer.isBuffer(data)) return data;
|
||||||
|
if (typeof data === 'string') return new Buffer(data);
|
||||||
|
throw new Error('invalid data type, must be string or buffer');
|
||||||
|
},
|
||||||
|
integerToShortString(value) {
|
||||||
|
return Number(value).toString(36);
|
||||||
|
},
|
||||||
|
isMatch(str, regStr) {
|
||||||
|
const re = new RegExp(regStr, 'gi');
|
||||||
|
return re.test(str);
|
||||||
|
},
|
||||||
|
/* 用正则表达式实现html转码*/
|
||||||
|
htmlEncodeByRegExp: function(str) {
|
||||||
|
let s = '';
|
||||||
|
if (str.length === 0) return '';
|
||||||
|
s = str.replace(/&/g, '&');
|
||||||
|
s = s.replace(/</g, '<');
|
||||||
|
s = s.replace(/>/g, '>');
|
||||||
|
s = s.replace(/ /g, ' ');
|
||||||
|
s = s.replace(/'/g, ''');
|
||||||
|
s = s.replace(/"/g, '"');
|
||||||
|
return s;
|
||||||
|
},
|
||||||
|
/* 用正则表达式实现html解码*/
|
||||||
|
htmlDecodeByRegExp: function(str) {
|
||||||
|
let s = '';
|
||||||
|
if (str.length === 0) return '';
|
||||||
|
s = str.replace(/&/g, '&');
|
||||||
|
s = s.replace(/</g, '<');
|
||||||
|
s = s.replace(/>/g, '>');
|
||||||
|
s = s.replace(/ /g, ' ');
|
||||||
|
s = s.replace(/'/g, '\'');
|
||||||
|
s = s.replace(/"/g, '"');
|
||||||
|
return s;
|
||||||
|
},
|
||||||
|
removeHtml(content, replceEnter) {
|
||||||
|
if (replceEnter) {
|
||||||
|
return content.replace(/<.+?>/g, '').replace(/\r\n/g, '<br/>').replace(/\s/g, '')
|
||||||
|
.replace(/(<br\/>)+/g, '<br/>').replace(/^<br\/>/, '').replace(/<br\/>$/, '');
|
||||||
|
} else {
|
||||||
|
return content.replace(/<.+?>/g, '').replace(/\s/g, '');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parseUrlObj(content) {
|
||||||
|
content = this.htmlDecodeByRegExp(content);
|
||||||
|
const re = /^.+?<title>(.+?)<\/title><des>(.+?)<\/des>.+?<url>(.+?)<\/url>.+?<thumburl>(.*?)<\/thumburl>.+?$/;
|
||||||
|
const contents = content.match(re);
|
||||||
|
const urlObj = {};
|
||||||
|
if (contents) {
|
||||||
|
if (contents.length > 1) urlObj.title = contents[1];
|
||||||
|
if (contents.length > 2) urlObj.desc = contents[2];
|
||||||
|
if (contents.length > 3) urlObj.url = contents[3];
|
||||||
|
if (contents.length > 4) urlObj.thumburl = contents[4];
|
||||||
|
}
|
||||||
|
return urlObj;
|
||||||
|
},
|
||||||
|
parseFileObj(content) {
|
||||||
|
content = this.htmlDecodeByRegExp(content);
|
||||||
|
const re = /^.+?<title>(.+?)<\/title>.+?<totallen>(.+?)<\/totallen>.+?<fileext>(.*?)<\/fileext>.+?$/;
|
||||||
|
|
||||||
|
const contents = content.match(re);
|
||||||
|
const fileObj = {};
|
||||||
|
if (contents) {
|
||||||
|
if (contents.length > 1) fileObj.title = contents[1];
|
||||||
|
if (contents.length > 2) fileObj.totallen = contents[2];
|
||||||
|
if (contents.length > 3) fileObj.fileext = contents[3];
|
||||||
|
}
|
||||||
|
return fileObj;
|
||||||
|
},
|
||||||
|
string10to62(number) {
|
||||||
|
const chars = '0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ'.split('');
|
||||||
|
const radix = chars.length;
|
||||||
|
let qutient = +number;
|
||||||
|
const arr = [];
|
||||||
|
do {
|
||||||
|
const mod = qutient % radix;
|
||||||
|
qutient = (qutient - mod) / radix;
|
||||||
|
arr.unshift(chars[mod]);
|
||||||
|
} while (qutient);
|
||||||
|
return arr.join('');
|
||||||
|
},
|
||||||
|
string62to10(numberCode) {
|
||||||
|
const chars = '0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ';
|
||||||
|
const radix = chars.length;
|
||||||
|
numberCode = String(numberCode);
|
||||||
|
const len = numberCode.length;
|
||||||
|
let i = 0;
|
||||||
|
let originNumber = 0;
|
||||||
|
while (i < len) {
|
||||||
|
originNumber += Math.pow(radix, i++) * chars.indexOf(numberCode.charAt(len - i) || 0);
|
||||||
|
}
|
||||||
|
return originNumber;
|
||||||
|
},
|
||||||
|
checkWithRes(content, res) {
|
||||||
|
let results = null;
|
||||||
|
res.some((re) => !!(results = content.match(re)));
|
||||||
|
return !(results === null || results.length === 0);
|
||||||
|
},
|
||||||
|
/* 移除微信图文消息中不支持的字符*/
|
||||||
|
replaceWxUnSupportChar(str) {
|
||||||
|
return str.replace(/&/g, '%26');
|
||||||
|
},
|
||||||
|
isArray(object) {
|
||||||
|
return object && typeof object === 'object' && Array === object.constructor;
|
||||||
|
},
|
||||||
|
/* 中文数字转number*/
|
||||||
|
chineseToNumber(chnStr) {
|
||||||
|
let rtn = 0;
|
||||||
|
let section = 0;
|
||||||
|
let number = 0;
|
||||||
|
let secUnit = false;
|
||||||
|
const str = chnStr.split('');
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
const num = chnNumChar[str[i]];
|
||||||
|
if (typeof num !== 'undefined') {
|
||||||
|
number = num;
|
||||||
|
if (i === str.length - 1) {
|
||||||
|
section += number;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const unit = chnNameValue[str[i]].value;
|
||||||
|
secUnit = chnNameValue[str[i]].secUnit;
|
||||||
|
if (secUnit) {
|
||||||
|
section = (section + number) * unit;
|
||||||
|
rtn += section;
|
||||||
|
section = 0;
|
||||||
|
} else {
|
||||||
|
section += (number * unit);
|
||||||
|
}
|
||||||
|
number = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rtn + section;
|
||||||
|
},
|
||||||
|
getReqRemoteIp(req) {
|
||||||
|
const ip = /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/;
|
||||||
|
const address = (req.headers['x-forwarded-for'] || '').split(',')[0] || req.ip;
|
||||||
|
const results = ip.exec(address);
|
||||||
|
return !results && results.length > 0 ? '' : results[0];
|
||||||
|
},
|
||||||
|
randomNum(minNum, maxNum) {
|
||||||
|
return parseInt(Math.random()*(maxNum-minNum+1)+minNum, 10);
|
||||||
|
},
|
||||||
|
randomNumAdv(minNum, maxNum) {
|
||||||
|
const x = format(crypto.randomBytes(4), 'dec');
|
||||||
|
return parseInt(x / Math.pow(2, 4 * 8) * (maxNum + 1 - minNum) + minNum);
|
||||||
|
},
|
||||||
|
string10to36(number) {
|
||||||
|
const chars = '0123456789abcdefghigklmnopqrstuvwxyz'.split('');
|
||||||
|
const radix = chars.length;
|
||||||
|
let qutient = +number;
|
||||||
|
const arr = [];
|
||||||
|
do {
|
||||||
|
const mod = qutient % radix;
|
||||||
|
qutient = (qutient - mod) / radix;
|
||||||
|
arr.unshift(chars[mod]);
|
||||||
|
} while (qutient);
|
||||||
|
return arr.join('');
|
||||||
|
},
|
||||||
|
string36to10(numberCode) {
|
||||||
|
const chars = '0123456789abcdefghigklmnopqrstuvwxyz';
|
||||||
|
const radix = chars.length;
|
||||||
|
numberCode = String(numberCode);
|
||||||
|
const len = numberCode.length;
|
||||||
|
let i = 0;
|
||||||
|
let originNumber = 0;
|
||||||
|
while (i < len) {
|
||||||
|
originNumber += Math.pow(radix, i++) * chars.indexOf(numberCode.charAt(len - i) || 0);
|
||||||
|
}
|
||||||
|
return originNumber;
|
||||||
|
},
|
||||||
|
};
|
128
src/utils/wechat.utils.js
Normal file
128
src/utils/wechat.utils.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import config from '../../config/config';
|
||||||
|
import request from 'request';
|
||||||
|
import Promise from 'bluebird';
|
||||||
|
import fs from 'fs';
|
||||||
|
import logger from './logger';
|
||||||
|
|
||||||
|
const refreshToken = function(appId, appSecret) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const link =
|
||||||
|
`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appId}&secret=${appSecret}`;
|
||||||
|
request(link, (err, response, body) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
const data = JSON.parse(body);
|
||||||
|
if (data.errcode) {
|
||||||
|
return reject(new Error(data.errmsg));
|
||||||
|
}
|
||||||
|
resolve(data.access_token);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 获取支付小程序的access token
|
||||||
|
* 如果global中有token,且未过去(超过2个小时), 则直接返回
|
||||||
|
* 否则刷新access token。
|
||||||
|
* 如果globa中有token,且时间不到5分钟,则先返回token,然后刷新token
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
getPayAccessToken() {
|
||||||
|
const appId = config.pay_weapp.app_id;
|
||||||
|
const appSecret = config.pay_weapp.app_secret;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const now = Math.round(new Date() / 1000);
|
||||||
|
if (global.accessToken && now < global.tokenExpireTime) {
|
||||||
|
resolve(global.accessToken);
|
||||||
|
if (global.tokenExpireTime - now < 5 * 60) {
|
||||||
|
process.nextTick(function() {
|
||||||
|
refreshToken(appId, appSecret)
|
||||||
|
.then(() => {
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
logger.error('refresh access token error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
refreshToken(appId, appSecret)
|
||||||
|
.then((token) => {
|
||||||
|
resolve(token);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/* 微信登录凭证校验*/
|
||||||
|
codeToSession(code) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const link =
|
||||||
|
`https://api.weixin.qq.com/sns/jscode2session?
|
||||||
|
appid=${config.weapp.app_id}&secret=${config.weapp.app_secret}&js_code=${code}&grant_type=authorization_code`;
|
||||||
|
request(link, (err, response, body) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
const data = JSON.parse(body);
|
||||||
|
const sessionKey = data.session_key;
|
||||||
|
const openId = data.openid;
|
||||||
|
resolve({openId, sessionKey});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
sendMsgToUser(accessToken, data) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const link = `https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=${accessToken}`;
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
url: link,
|
||||||
|
headers: {
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: data,
|
||||||
|
json: true,
|
||||||
|
};
|
||||||
|
request(options, (err, response, body) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
resolve(body);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
generateQr(appId, appSecret, scene, filePath) {
|
||||||
|
const stream = fs.createWriteStream(filePath);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
refreshToken(appId, appSecret)
|
||||||
|
.then((token) => {
|
||||||
|
const link = `https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=${token}`;
|
||||||
|
const data = {
|
||||||
|
scene: scene,
|
||||||
|
width: 430,
|
||||||
|
auto_color: false,
|
||||||
|
line_color: {'r': '0', 'g': '0', 'b': '0'},
|
||||||
|
};
|
||||||
|
const options = {
|
||||||
|
method: 'POST',
|
||||||
|
url: link,
|
||||||
|
headers: {
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: data,
|
||||||
|
json: true,
|
||||||
|
};
|
||||||
|
request(options)
|
||||||
|
.pipe(stream)
|
||||||
|
.on('close', resolve);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
62
test/test.js
Normal file
62
test/test.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
var express = require('express');
|
||||||
|
var ldap = require('ldapjs');
|
||||||
|
|
||||||
|
var app = express();
|
||||||
|
|
||||||
|
//创建LDAP client,把服务器url传入
|
||||||
|
var client = ldap.createClient({
|
||||||
|
url: 'ldap://ldap.kingsome.cn:389'
|
||||||
|
});
|
||||||
|
|
||||||
|
//创建LDAP查询选项
|
||||||
|
//filter的作用就是相当于SQL的条件
|
||||||
|
var opts = {
|
||||||
|
filter: '(uid=yulixing)', //查询条件过滤器,查找uid=kxh的用户节点
|
||||||
|
scope: 'sub', //查询范围
|
||||||
|
timeLimit: 500 //查询超时
|
||||||
|
};
|
||||||
|
|
||||||
|
var user = [];
|
||||||
|
app.get('/', function(req, res, next) {
|
||||||
|
//将client绑定LDAP Server
|
||||||
|
//第一个参数:是用户,必须是从根节点到用户节点的全路径
|
||||||
|
//第二个参数:用户密码
|
||||||
|
client.bind('cn=admin,dc=kingsome,dc=cn', 'milesQWE321', function(err, res1) {
|
||||||
|
//开始查询
|
||||||
|
//第一个参数:查询基础路径,代表在查询用户信心将在这个路径下进行,这个路径是由根节开始
|
||||||
|
//第二个参数:查询选项
|
||||||
|
client.search('ou=people,dc=kingsome,dc=cn', opts, function(err, res2) {
|
||||||
|
console.log(res2)
|
||||||
|
//查询结果事件响应
|
||||||
|
res2.on('searchEntry', function(entry) {
|
||||||
|
//获取查询的对象
|
||||||
|
var user = entry.object;
|
||||||
|
var userText = JSON.stringify(user, null, 2);
|
||||||
|
users = entry
|
||||||
|
// console.log(entry)
|
||||||
|
// console.log(userText);
|
||||||
|
});
|
||||||
|
|
||||||
|
res2.on('searchReference', function(referral) {
|
||||||
|
console.log('referral: ' + referral.uris.join());
|
||||||
|
});
|
||||||
|
|
||||||
|
//查询错误事件
|
||||||
|
res2.on('error', function(err) {
|
||||||
|
console.error('error: ' + err.message);
|
||||||
|
//unbind操作,必须要做
|
||||||
|
client.unbind();
|
||||||
|
});
|
||||||
|
|
||||||
|
//查询结束
|
||||||
|
res2.on('end', function(result) {
|
||||||
|
console.log('search status: ' + result);
|
||||||
|
//unbind操作,必须要做
|
||||||
|
client.unbind();
|
||||||
|
});
|
||||||
|
res.send({})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen('6789');
|
88
test/test2.js
Normal file
88
test/test2.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
var express = require('express');
|
||||||
|
var ldap = require('ldapjs');
|
||||||
|
|
||||||
|
var app = express();
|
||||||
|
|
||||||
|
//创建LDAP client,把服务器url传入
|
||||||
|
var client = ldap.createClient({
|
||||||
|
url: 'ldap://ldap.kingsome.cn:389'
|
||||||
|
});
|
||||||
|
|
||||||
|
//创建LDAP查询选项
|
||||||
|
//filter的作用就是相当于SQL的条件
|
||||||
|
var opts = {
|
||||||
|
// filter: '(objectClass=posixAccount)', //查询条件过滤器,查找uid=kxh的用户节点
|
||||||
|
filter: '(uid=yulixing1)', //查询条件过滤器,查找uid=kxh的用户节点
|
||||||
|
scope: 'sub', //查询范围
|
||||||
|
timeLimit: 500 //查询超时
|
||||||
|
};
|
||||||
|
|
||||||
|
var user = [];
|
||||||
|
app.get('/', function(req, res, next) {
|
||||||
|
//将client绑定LDAP Server
|
||||||
|
//第一个参数:是用户,必须是从根节点到用户节点的全路径
|
||||||
|
//第二个参数:用户密码
|
||||||
|
client.bind('cn=admin,dc=kingsome,dc=cn', 'milesQWE321', function(err, res1) {
|
||||||
|
console.log(err);
|
||||||
|
//开始查询
|
||||||
|
//第一个参数:查询基础路径,代表在查询用户信心将在这个路径下进行,这个路径是由根节开始
|
||||||
|
//第二个参数:查询选项
|
||||||
|
client.search('ou=people,dc=kingsome,dc=cn', opts, function(err, res2) {
|
||||||
|
var entries = [];
|
||||||
|
//查询结果事件响应
|
||||||
|
res2.on('searchEntry', function(entry) {
|
||||||
|
//获取查询的对象
|
||||||
|
var user = entry.object;
|
||||||
|
entries.push(user);
|
||||||
|
users = entry;
|
||||||
|
});
|
||||||
|
|
||||||
|
res2.on('searchReference', function(referral) {
|
||||||
|
console.log('referral: ' + referral.uris.join());
|
||||||
|
});
|
||||||
|
|
||||||
|
//查询错误事件
|
||||||
|
res2.on('error', function(err) {
|
||||||
|
console.error('error: ' + err.message);
|
||||||
|
//unbind操作,必须要做
|
||||||
|
client.unbind();
|
||||||
|
});
|
||||||
|
|
||||||
|
//查询结束
|
||||||
|
res2.on('end', function(result) {
|
||||||
|
console.log('search status: ' + result);
|
||||||
|
console.log(entries)
|
||||||
|
if (entries.length !== 0) {
|
||||||
|
client.bind(entries[0].dn, 'yulixing123456', function(
|
||||||
|
err,
|
||||||
|
res3
|
||||||
|
) {
|
||||||
|
if (err) {
|
||||||
|
res.send({
|
||||||
|
err: err,
|
||||||
|
errmsg: err.message
|
||||||
|
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
res.send({
|
||||||
|
result: entries,
|
||||||
|
state: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.send({
|
||||||
|
msg: '登录失败'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// res.send({
|
||||||
|
// entries
|
||||||
|
// })
|
||||||
|
//unbind操作,必须要做
|
||||||
|
client.unbind();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen('6789');
|
Loading…
x
Reference in New Issue
Block a user