Browse Source

INIT

release_0.0.1
yuan_yi 3 years ago
parent
commit
f8ddd97972
  1. 41
      code/api/.vscode/launch.json
  2. 14
      code/api/Dockerfile
  3. 3
      code/api/app/index.js
  4. 189
      code/api/app/lib/controllers/auth/index.js
  5. 23
      code/api/app/lib/index.js
  6. 83
      code/api/app/lib/middlewares/api-log.js
  7. 150
      code/api/app/lib/middlewares/authenticator.js
  8. 79
      code/api/app/lib/models/user.js
  9. 43
      code/api/app/lib/models/user_token.js
  10. 32
      code/api/app/lib/routes/auth/index.js
  11. 17
      code/api/app/lib/routes/index.js
  12. 103
      code/api/config.js
  13. 35
      code/api/package.json
  14. 12
      code/api/server.js
  15. 15
      code/api/utils/forward-api.js
  16. 3257
      code/api/yarn.lock

41
code/api/.vscode/launch.json

@ -0,0 +1,41 @@
{
// 使 IntelliSense
//
// 访: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "启动API",
"program": "${workspaceRoot}/server.js",
"env": {
"NODE_ENV": "development"
},
"args": [
"-p 14000",
"-f http://localhost:14000",
// "-g postgres://postgres:123@10.8.30.32:5432/yinjiguanli",
// "-g postgres://postgres:123456@221.230.55.27:5432/yinjiguanli",
// "-g postgres://FashionAdmin:123456@10.8.30.156:5432/SmartEmergency",
"-g postgres://postgres:Mantis1921@116.63.50.139:54327/smartYingji"
]
},
{
"type": "node",
"request": "launch",
"name": "run mocha",
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
"stopOnEntry": false,
"args": [
"app/test/*.test.js",
"--no-timeouts"
],
"cwd": "${workspaceRoot}",
"runtimeExecutable": null,
"env": {
"NODE_ENV": "development"
}
}
]
}

14
code/api/Dockerfile

@ -0,0 +1,14 @@
FROM repository.anxinyun.cn/base-images/nodejs12:20.10.12.2
MAINTAINER liuxinyi "liu.xinyi@free-sun.com.cn"
COPY . /var/app
WORKDIR /var/app
EXPOSE 8080
CMD ["-g", "postgres://FashionAdmin:123456@iota-m1:5433/SmartRiver", "--qnak", "5XrM4wEB9YU6RQwT64sPzzE6cYFKZgssdP5Kj3uu", "--qnsk", "w6j2ixR_i-aelc6I7S3HotKIX-ukMzcKmDfH6-M5", "--qnbkt", "anxinyun-test", "--qndmn", "http://test.resources.anxinyun.cn"]
ENTRYPOINT [ "node", "server.js" ]

3
code/api/app/index.js

@ -0,0 +1,3 @@
'use strict';
module.exports = require('./lib');

189
code/api/app/lib/controllers/auth/index.js

@ -0,0 +1,189 @@
'use strict';
const Hex = require('crypto-js/enc-hex');
const MD5 = require('crypto-js/md5');
const moment = require('moment');
const uuid = require('uuid');
async function login(ctx, next) {
const transaction = await ctx.fs.dc.orm.transaction();
try {
const models = ctx.fs.dc.models;
const params = ctx.request.body;
let password = Hex.stringify(MD5(params.password));
const userRes = await models.User.findOne({
where: {
username: params.username,
password: password,
delete: false,
},
attributes: { exclude: ['password'] },
include: [{
attributes: ["resourceId"],
model: models.UserResource
}]
});
if (!userRes) {
ctx.status = 400;
ctx.body = {
"message": "账号或密码错误"
}
} else if (!userRes.enable) {
ctx.status = 400;
ctx.body = { message: "该用户已被禁用" }
} else {
const token = uuid.v4();
let userRslt = Object.assign(userRes.dataValues, {
authorized: true,
token: token,
userResources: userRes.userResources.map(r => r.resourceId),
});
await models.UserToken.create({
token: token,
userInfo: userRslt,
expired: moment().add(30, 'days').format()
});
ctx.status = 200;
ctx.body = userRslt;
}
await transaction.commit();
} catch (error) {
await transaction.rollback();
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "登录失败"
}
}
}
/**
* 微信小程序登录
* @@requires.body {phone-手机号, password-密码} ctx
*/
async function wxLogin(ctx, next) {
const transaction = await ctx.fs.dc.orm.transaction();
try {
const models = ctx.fs.dc.models;
const params = ctx.request.body;
let password = Hex.stringify(MD5(params.password));
const userRes = await models.User.findOne({
where: {
phone: params.phone,
password: password,
delete: false,
},
attributes: { exclude: ['password'] }
});
if (!userRes) {
ctx.status = 400;
ctx.body = { message: "手机号或密码错误" }
} else if (!userRes.enable) {
ctx.status = 400;
ctx.body = { message: "该用户已被禁用" }
} else {
const token = uuid.v4();
//获取用户关注区域信息
const departmentRes = await models.Department.findOne({ where: { id: userRes.departmentId } });
let attentionRegion = departmentRes;
while (attentionRegion.dependence && attentionRegion.type != 1) {
const departmentParent = await models.Department.findOne({ where: { id: attentionRegion.dependence } });
attentionRegion = {
...departmentParent.dataValues,
nextRegin: attentionRegion
}
}
//获取用户权限信息
const resourceRes = await models.UserResource.findAll({
where: {
userId: userRes.id
},
include: [{
model: models.Resource,
attributes: ['code', 'name'],
}],
attributes: []
});
let userRslt = Object.assign({
authorized: true,
token: token,
...userRes.dataValues
});
await models.UserToken.create({
token: token,
userInfo: userRslt,
expired: moment().add(30, 'day').format('YYYY-MM-DD HH:mm:ss')
}, { transaction: transaction });
ctx.status = 200;
ctx.body = Object.assign({
...userRslt,
userRegionType: departmentRes.type,//1-市级,2-区县级,3-乡镇级,4-村级
attentionRegion: attentionRegion,
resources: resourceRes.map(r => r.resource)
});
}
await transaction.commit();
} catch (error) {
await transaction.rollback();
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "登录失败"
}
}
}
async function logout(ctx) {
try {
const { token, code } = ctx.request.body;
const models = ctx.fs.dc.models;
await models.UserToken.destroy({
where: {
token: token,
},
});
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "登出失败"
}
}
}
/**
* 微信小程序登出
* @request.body {token-用户登录Token} ctx
*/
async function wxLogout(ctx) {
try {
const { token } = ctx.request.body;
const models = ctx.fs.dc.models;
await models.UserToken.destroy({
where: {
token: token,
},
});
ctx.status = 204;
} catch (error) {
ctx.fs.logger.error(`path: ${ctx.path}, error: ${error}`);
ctx.status = 400;
ctx.body = {
"message": "登出失败"
}
}
}
module.exports = {
login,
wxLogin,
logout,
wxLogout
};

23
code/api/app/lib/index.js

@ -0,0 +1,23 @@
'use strict';
const routes = require('./routes');
const authenticator = require('./middlewares/authenticator');
// const apiLog = require('./middlewares/api-log');
module.exports.entry = function (app, router, opts) {
app.fs.logger.log('info', '[FS-AUTH]', 'Inject auth and api mv into router.');
app.fs.api = app.fs.api || {};
app.fs.api.authAttr = app.fs.api.authAttr || {};
app.fs.api.logAttr = app.fs.api.logAttr || {};
router.use(authenticator(app, opts));
// router.use(apiLog(app, opts));
router = routes(app, router, opts);
};
module.exports.models = function (dc) { // dc = { orm: Sequelize对象, ORM: Sequelize, models: {} }
require('./models/user')(dc);
require('./models/user_token')(dc);
};

83
code/api/app/lib/middlewares/api-log.js

@ -0,0 +1,83 @@
/**
* Created by PengPeng on 2017/4/26.
*/
'use strict';
const moment = require('moment');
const pathToRegexp = require('path-to-regexp');
function factory(app, opts) {
async function sendToEsAsync(producer, payloads) {
return new Promise((resolve, reject) => {
producer.send(payloads, function (err) {
if (err) {
reject(err);
} else {
resolve();
}
});
})
}
async function logger(ctx, next) {
const { path, method } = ctx;
const start = Date.now();
// 等待路由处理
await next();
try {
let logAttr = null;
for (let prop in app.fs.api.logAttr) {
let keys = [];
let re = pathToRegexp(prop.replace(/\:[A-Za-z_\-]+\b/g, '(\\d+)'), keys);
if (re.test(`${method}${path}`)) {
logAttr = app.fs.api.logAttr[prop];
break;
}
}
let parameter = null, parameterShow = null, user_id, _token, app_key;
if (ctx.fs.api) {
const { actionParameter, actionParameterShow, userId, token, appKey } = ctx.fs.api;
parameter = actionParameter;
parameterShow = actionParameterShow;
user_id = userId;
_token = token;
app_key = appKey;
}
const producer = ctx.fs.kafka.producer;
const message = {
log_time: moment().toISOString(),
method: method,
content: logAttr ? logAttr.content : '',
parameter: JSON.stringify(parameter) || JSON.stringify(ctx.request.body),
parameter_show: parameterShow,
visible: logAttr ? logAttr.visible : true,
cost: Date.now() - start,
status_code: ctx.status,
url: ctx.request.url,
user_agent: ctx.request.headers["user-agent"],
user_id: user_id,
session: _token,
app_key: app_key,
header: JSON.stringify(ctx.request.headers),
ip: ctx.request.headers["x-real-ip"] || ctx.ip
};
const payloads = [{
topic: `${opts.kafka.topicPrefix}`,
messages: [JSON.stringify(message)],
partition: 0
}];
// await sendToEsAsync(producer, payloads);
} catch (e) {
ctx.fs.logger.error(`日志记录失败: ${e}`);
}
}
return logger;
}
module.exports = factory;

150
code/api/app/lib/middlewares/authenticator.js

@ -0,0 +1,150 @@
/**
* Created by PengLing on 2017/3/27.
*/
'use strict';
const pathToRegexp = require('path-to-regexp');
const util = require('util');
const moment = require('moment');
class ExcludesUrls {
constructor(opts) {
this.allUrls = undefined;
this.reload(opts);
}
sanitizePath(path) {
if (!path) return '/';
const p = '/' + path.replace(/^\/+/i, '').replace(/\/+$/, '').replace(/\/{2,}/, '/');
return p;
}
reload(opts) {
// load all url
if (!this.allUrls) {
this.allUrls = opts;
let that = this;
this.allUrls.forEach(function (url, i, arr) {
if (typeof url === "string") {
url = { p: url, o: '*' };
arr[i] = url;
}
const keys = [];
let eachPath = url.p;
url.p = (!eachPath || eachPath === '(.*)' || util.isRegExp(eachPath)) ? eachPath : that.sanitizePath(eachPath);
url.pregexp = pathToRegexp(eachPath, keys);
});
}
}
isExcluded(path, method) {
return this.allUrls.some(function (url) {
return !url.auth
&& url.pregexp.test(path)
&& (url.o === '*' || url.o.indexOf(method) !== -1);
});
}
}
/**
* 判断Url是否不鉴权
* @param {*} opts {exclude: [*] or []}'*'['*']:跳过所有路由; []:所有路由都要验证
* @param {*} path 当前request的path
* @param {*} method 当前request的method
*/
let isPathExcluded = function (opts, path, method) {
let excludeAll = Boolean(opts.exclude && opts.exclude.length && opts.exclude[0] == '*');
let excludes = null;
if (!excludeAll) {
let excludeOpts = opts.exclude || [];
excludeOpts.push({ p: '/login', o: 'POST' });
excludeOpts.push({ p: '/wxLogin', o: 'POST' });
excludeOpts.push({ p: '/logout', o: 'PUT' });
excludeOpts.push({ p: '/wxLogout', o: 'PUT' });
excludes = new ExcludesUrls(excludeOpts);
}
let excluded = excludeAll || excludes.isExcluded(path, method);
return excluded;
};
let authorizeToken = async function (ctx, token) {
let rslt = null;
const tokenFormatRegexp = /^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$/g;
if (token && tokenFormatRegexp.test(token)) {
try {
const axyRes = await ctx.fs.dc.models.UserToken.findOne({
where: {
token: token,
expired: { $gte: moment().format('YYYY-MM-DD HH:mm:ss') }
}
});
const { userInfo, expired } = axyRes;
if (!expired || moment().valueOf() <= moment(expired).valueOf()) {
rslt = {
'authorized': userInfo.authorized,
'resources': (userInfo || {}).resources || [],
};
ctx.fs.api.userId = userInfo.id;
ctx.fs.api.userInfo = userInfo;
ctx.fs.api.token = token;
}
} catch (err) {
const { error } = err.response || {};
ctx.fs.logger.log('[anxinyun]', '[AUTH] failed', (error || {}).message || `cannot GET /users/${token}`);
}
}
return rslt;
};
let isResourceAvailable = function (resources, options) {
let authCode = null;
// authorize user by authorization attribute
const { authAttr, method, path } = options;
console.log(resources, options)
for (let prop in authAttr) {
let keys = [];
let re = pathToRegexp(prop.replace(/\:[A-Za-z_\-]+\b/g, '(\\d+)'), keys);
if (re.test(`${method}${path}`)) {
authCode = authAttr[prop];
break;
}
}
return !authCode || (resources || []).some(code => code === authCode);
};
function factory(app, opts) {
return async function auth(ctx, next) {
const { path, method, header, query } = ctx;
ctx.fs.logger.log('[AUTH] start', path, method);
ctx.fs.api = ctx.fs.api || {};
ctx.fs.port = opts.port;
ctx.redis = app.redis;
let error = null;
if (path) {
if (!isPathExcluded(opts, path, method)) {
const user = await authorizeToken(ctx, header.token || query.token);
if (user && user.authorized) {
// if (!isResourceAvailable(user.resources, { authAttr: app.fs.auth.authAttr, path, method })) {
// error = { status: 403, name: 'Forbidden' }
// } else {
// error = { status: 401, name: 'Unauthorized' }
// }
} else {
error = { status: 401, name: 'Unauthorized' }
}
}
} else {
error = { status: 401, name: 'Unauthorized' };
}
if (error) {
ctx.fs.logger.log('[AUTH] failed', path, method);
ctx.status = error.status;
ctx.body = error.name;
} else {
ctx.fs.logger.log('[AUTH] passed', path, method);
await next();
}
}
}
module.exports = factory;

79
code/api/app/lib/models/user.js

@ -0,0 +1,79 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const User = sequelize.define("user", {
id: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "id",
autoIncrement: true,
unique: "user_id_uindex"
},
name: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "name",
autoIncrement: false
},
namePresent: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "name_present",
autoIncrement: false
},
password: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "password",
autoIncrement: false
},
phone: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "phone",
autoIncrement: false
},
email: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "email",
autoIncrement: false
},
enabled: {
type: DataTypes.BOOLEAN,
allowNull: true,
defaultValue: null,
comment: null,
primaryKey: false,
field: "enabled",
autoIncrement: false
}
}, {
tableName: "user",
comment: "",
indexes: []
});
dc.models.User = User;
return User;
};

43
code/api/app/lib/models/user_token.js

@ -0,0 +1,43 @@
/* eslint-disable*/
'use strict';
module.exports = dc => {
const DataTypes = dc.ORM;
const sequelize = dc.orm;
const UserToken = sequelize.define("userToken", {
token: {
type: DataTypes.STRING,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: true,
field: "token",
autoIncrement: false,
unique: "user_token_token_uindex"
},
userInfo: {
type: DataTypes.JSONB,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "user_info",
autoIncrement: false
},
expired: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: null,
comment: null,
primaryKey: false,
field: "expired",
autoIncrement: false
}
}, {
tableName: "user_token",
comment: "",
indexes: []
});
dc.models.UserToken = UserToken;
return UserToken;
};

32
code/api/app/lib/routes/auth/index.js

@ -0,0 +1,32 @@
'use strict';
const auth = require('../../controllers/auth');
module.exports = function (app, router, opts) {
/**
* @api {Post} login 登录.
* @apiVersion 1.0.0
* @apiGroup Auth
*/
app.fs.api.logAttr['POST/login'] = { content: '登录', visible: true };
router.post('/login', auth.login);
/**
* @api {POST} wxLogin 微信小程序登录.使用手机号密码登录
* @apiVersion 1.0.0
* @apiGroup Auth
*/
app.fs.api.logAttr['POST/wxLogin'] = { content: '微信小程序登录', visible: true };
router.post('/wxLogin', auth.wxLogin);
app.fs.api.logAttr['PUT/logout'] = { content: '登出', visible: false };
router.put('/logout', auth.logout);
/**
* @api {PUT} wxLogout 微信小程序登出
* @apiVersion 1.0.0
* @apiGroup Auth
*/
app.fs.api.logAttr['PUT/wxLogout'] = { content: '登出', visible: false };
router.put('/wxLogout', auth.wxLogout);
};

17
code/api/app/lib/routes/index.js

@ -0,0 +1,17 @@
'use strict';
const path = require('path');
const fs = require('fs');
module.exports = function (app, router, opts) {
fs.readdirSync(__dirname).forEach((filename) => {
if (filename.indexOf('.') !== 0 &&fs.lstatSync(path.join(__dirname, filename)).isDirectory()) {
fs.readdirSync(path.join(__dirname, filename)).forEach((api) => {
if (api.indexOf('.') == 0 || api.indexOf('.js') == -1) return;
require(`./${filename}/${api}`)(app, router, opts);
});
}
});
return router;
};

103
code/api/config.js

@ -0,0 +1,103 @@
'use strict';
/*jslint node:true*/
const path = require('path');
const os = require('os');
const moment = require('moment');
const args = require('args');
const dev = process.env.NODE_ENV == 'development';
// 启动参数
args.option(['p', 'port'], '启动端口');
args.option(['g', 'pg'], 'postgre服务URL');
args.option(['f', 'fileHost'], '文件中心本地化存储: WebApi 服务器地址(必填), 该服务器提供文件上传Web服务');
const flags = args.parse(process.argv);
const IOT_VIDEO_ACCESS_DB = process.env.IOT_VIDEO_ACCESS_DB || flags.pg;
const IOT_VIDEO_ACCESS_LOCAL_SVR_ORIGIN = process.env.IOT_VIDEO_ACCESS_LOCAL_SVR_ORIGIN || flags.fileHost;
if (!IOT_VIDEO_ACCESS_DB) {
console.log('缺少启动参数,异常退出');
args.showHelp();
process.exit(-1);
}
const product = {
port: flags.port || 8080,
staticDirs: ['static'],
mws: [
{
entry: require('@fs/attachment').entry,
opts: {
local: {
origin: IOT_VIDEO_ACCESS_LOCAL_SVR_ORIGIN || `http://localhost:${flags.port || 8080}`,
rootPath: 'static',
childPath: 'upload',
},
maxSize: 104857600, // 100M
}
}, {
entry: require('./app').entry,
opts: {
exclude: [], // 不做认证的路由,也可以使用 exclude: ["*"] 跳过所有路由
}
}
],
dc: {
url: IOT_VIDEO_ACCESS_DB,
opts: {
pool: {
max: 80,
min: 10,
idle: 10000
},
define: {
freezeTableName: true, // 固定表名
timestamps: false // 不含列 "createAt"/"updateAt"/"DeleteAt"
},
timezone: '+08:00',
logging: false
},
models: [require('./app').models]
},
logger: {
level: 'info',
json: false,
filename: path.join(__dirname, 'log', 'runtime.log'),
colorize: false,
maxsize: 1024 * 1024 * 5,
rotationFormat: false,
zippedArchive: true,
maxFiles: 10,
prettyPrint: true,
label: '',
timestamp: () => moment().format('YYYY-MM-DD HH:mm:ss.SSS'),
eol: os.EOL,
tailable: true,
depth: null,
showLevel: true,
maxRetries: 1
}
};
const development = {
port: product.port,
staticDirs: product.staticDirs,
mws: product.mws,
dc: product.dc,
logger: product.logger
};
if (dev) {
// mws
for (let mw of development.mws) {
// if (mw.opts.exclude) mw.opts.exclude = ['*']; // 使用 ['*'] 跳过所有路由
}
// logger
development.logger.filename = path.join(__dirname, 'log', 'development.log');
development.logger.level = 'debug';
development.dc.opts.logging = console.log;
}
module.exports = dev ? development : product;

35
code/api/package.json

@ -0,0 +1,35 @@
{
"name": "iot-auth",
"version": "1.0.0",
"description": "fs iot-auth api",
"main": "server.js",
"scripts": {
"test": "set DEBUG=true&&\"node_modules/.bin/mocha\" --harmony --reporter spec app/test/*.test.js",
"start": "set NODE_ENV=development&&node server -p 4200 -g postgres://postgres:123@10.8.30.32:5432/iot_auth -f http://localhost:4200",
"start:linux": "export NODE_ENV=development&&node server -p 4200 -g postgres://postgres:123@10.8.30.32:5432/iot_auth"
},
"author": "",
"license": "MIT",
"repository": {},
"dependencies": {
"@fs/attachment": "^1.0.0",
"args": "^3.0.7",
"crypto-js": "^4.0.0",
"file-saver": "^2.0.2",
"fs-web-server-scaffold": "^2.0.2",
"ioredis": "^4.19.4",
"koa-convert": "^1.2.0",
"koa-proxy": "^0.9.0",
"moment": "^2.24.0",
"path": "^0.12.7",
"path-to-regexp": "^3.0.0",
"pg": "^7.9.0",
"redis": "^3.1.2",
"request": "^2.88.2",
"superagent": "^3.5.2",
"uuid": "^3.3.2"
},
"devDependencies": {
"mocha": "^6.0.2"
}
}

12
code/api/server.js

@ -0,0 +1,12 @@
/**
* Created by rain on 2016/1/25.
*/
'use strict';
/*jslint node:true*/
//from koa
const scaffold = require('fs-web-server-scaffold');
const config = require('./config');
module.exports = scaffold(config);

15
code/api/utils/forward-api.js

@ -0,0 +1,15 @@
'use strict';
const proxy = require('koa-proxy');
const convert = require('koa-convert');
module.exports = {
entry: function (app, router, opts) {
app.use(convert(proxy({
host: opts.host,
match: opts.match,
map: function (path) {
return path.replace(opts.match, '');
}
})));
}
};

3257
code/api/yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save