This commit is contained in:
yulixing 2019-05-13 20:14:39 +08:00
commit b978b16e6a
48 changed files with 3897 additions and 0 deletions

3
.babelrc Normal file
View File

@ -0,0 +1,3 @@
{
"presets": ["env"]
}

22
.gitignore vendored Normal file
View 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
View 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
View 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
View 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;

View 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
View 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};

View 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
View 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);

View 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;

View 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);

View 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;

View 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类型跳转实际urlweapp类型的话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
View 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;

View 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;

View 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;

View 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;

View 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
View 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;

View 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;

View 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;

View 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;

View 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);

View 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);

View 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;

View 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);

View 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
View 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;

View 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;

View 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;

View 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;

View 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
View 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

View 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
View 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
View 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
View 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
View 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
View 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;
}
},
};

View 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
View 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
View 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
View 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
View 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
View 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, '&amp;');
s = s.replace(/</g, '&lt;');
s = s.replace(/>/g, '&gt;');
s = s.replace(/ /g, '&nbsp;');
s = s.replace(/'/g, '&#39;');
s = s.replace(/"/g, '&quot;');
return s;
},
/* 用正则表达式实现html解码*/
htmlDecodeByRegExp: function(str) {
let s = '';
if (str.length === 0) return '';
s = str.replace(/&amp;/g, '&');
s = s.replace(/&lt;/g, '<');
s = s.replace(/&gt;/g, '>');
s = s.replace(/&nbsp;/g, ' ');
s = s.replace(/&#39;/g, '\'');
s = s.replace(/&quot;/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
View 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
View 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
View 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');