Commit 7e91c276 authored by 成旭东's avatar 成旭东

fix

parent f94bb1d9
Pipeline #27916 passed with stage
in 3 minutes 57 seconds
......@@ -11,10 +11,8 @@ logs/
app/public
*.swp
*.lock
*.js
!.autod.conf.js
app/**/*.js
test/**/*.js
config/**/*.js
app/**/*.map
......
module.exports = function(agent) {};
module.exports = app => {
// 应用会等待这个函数执行完成才启动
app.beforeStart(async () => {
async function initDBPermission(permission) {
const {
name,
method,
description,
} = permission;
const { model } = app;
const [ entity ] = await model.User.Role.Permission.findOrCreate({
where: {
name,
method,
},
defaults: {
name,
method,
description,
},
});
return entity;
}
async function initDBMenu(menu, parent_id = 0) {
const {
label,
url,
icon,
} = menu;
const { model } = app;
let ret = [];
const [ entity ] = await model.User.Role.Menu.findOrCreate({
where: {
label,
parent_id,
},
defaults: {
label,
url,
icon,
parent_id,
},
});
ret.push(entity);
if (menu.children) {
for (let i = 0; i < menu.children.length; i++) {
const tmp = await initDBMenu(menu.children[i], entity.id);
ret = ret.concat(tmp);
}
}
if (menu.permissions) {
for (let i = 0; i < menu.permissions.length; i++) {
const permissionEntity = await initDBPermission(menu.permissions[i]);
await entity.addPermission(permissionEntity);
}
}
return ret;
}
async function initDBRole(role, parent_id = 0) {
const {
name,
tag,
} = role;
const { model } = app;
const [ entity ] = await model.User.Role.findOrCreate({
where: {
tag,
},
defaults: {
name,
tag,
is_automatic: 0,
is_system: 1,
parent_id,
},
});
if (role.children) {
for (let i = 0; i < role.children.length; i++) {
await initDBRole(role.children[i], entity.id);
}
}
if (role.permissions) {
for (let i = 0; i < role.permissions.length; i++) {
const permissionEntity = await initDBPermission(role.permissions[i]);
await entity.addPermission(permissionEntity);
}
}
if (role.menus) {
for (let i = 0; i < role.menus.length; i++) {
const menuEntities = await initDBMenu(role.menus[i]);
for (let j = 0; j < menuEntities.length; j++) {
await entity.addMenu(menuEntities[j]);
}
}
}
}
async function initDB() {
const init_database = app.config.init_database;
for (let i = 0; i < init_database.roles.length; i++) {
const item = init_database.roles[i];
await initDBRole(item);
}
}
if (app.config.initDB) {
await initDB();
}
});
};
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE } = app.Sequelize;
const User = app.model.define('user', {
id: { type: INTEGER, primaryKey: true, autoIncrement: true },
name: STRING(30),
position: STRING(30),
unionid: STRING(30),
userid: STRING(30),
dingid: STRING(30),
avatar: STRING(255),
email: STRING(30),
openid: STRING(30),
mobile: STRING(12),
created_at: {
type: DATE,
get() {
const date = this.getDataValue('created_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_at: {
type: DATE,
get() {
const date = this.getDataValue('updated_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
tableName: 'uc_user',
});
return User;
};
const moment = require('moment');
module.exports = app => {
const { DataTypes } = app.Sequelize;
const model = app.wafangModel.define('model', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
},
project_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
comment: '预约的楼盘的id',
},
user_id: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '用户编号',
},
phone: {
type: DataTypes.STRING(11),
allowNull: false,
comment: '预约手机号',
},
status: {
type: DataTypes.ENUM('待联系', '已联系'),
allowNull: false,
defaultValue: '待联系',
comment: '操作状态',
},
operation_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'operation_at',
get() {
const date = this.getDataValue('operation_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
comment: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '备注信息',
},
valid: {
type: DataTypes.INTEGER(4),
allowNull: false,
defaultValue: 1,
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
field: 'created_at',
get() {
const date = this.getDataValue('created_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'updated_at',
get() {
const date = this.getDataValue('updated_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
tableName: 'wafang_appointment',
});
model.associate = function() {
app.wafangModel.WafangAppointment.belongsTo(app.wafangModel.WafangProject, { as: 'project', sourceKey: 'project_id', foreignKey: 'id' });
};
return model;
};
const moment = require('moment');
module.exports = app => {
const { DataTypes } = app.Sequelize;
const model = app.wafangModel.define('model', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
},
title: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '配置项标题',
},
description: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '简要描述',
},
alias: {
type: DataTypes.STRING(100),
allowNull: false,
comment: '别名',
},
status: {
type: DataTypes.ENUM('online', 'offline'),
allowNull: false,
defaultValue: 'online',
comment: '上下线',
},
valid: {
type: DataTypes.INTEGER(1),
allowNull: false,
defaultValue: 1,
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
field: 'created_at',
get() {
const date = this.getDataValue('created_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'updated_at',
get() {
const date = this.getDataValue('updated_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
tableName: 'wafang_block',
});
model.associate = function() {
app.wafangModel.WafangBlock.hasMany(app.wafangModel.WafangBlockContent, { as: 'blockContent', sourceKey: 'id', foreignKey: 'block_id' });
};
return model;
};
const moment = require('moment');
module.exports = app => {
const { DataTypes } = app.Sequelize;
return app.wafangModel.define('model', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
},
block_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
comment: '块配置id',
},
title: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '标题',
},
sub_title: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '副标题',
},
description: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '描述',
},
image: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '图片地址',
},
url: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '跳转链接',
},
order: {
type: DataTypes.INTEGER(11),
allowNull: false,
defaultValue: 0,
comment: '排序',
},
status: {
type: DataTypes.ENUM('online', 'offline'),
allowNull: false,
defaultValue: 'offline',
comment: '上下线',
},
valid: {
type: DataTypes.INTEGER(1),
allowNull: false,
defaultValue: 1,
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
field: 'created_at',
get() {
const date = this.getDataValue('created_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'updated_at',
get() {
const date = this.getDataValue('updated_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
tableName: 'wafang_block_content',
});
};
const moment = require('moment');
module.exports = app => {
const { DataTypes } = app.Sequelize;
return app.wafangModel.define('model', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
},
type: {
type: DataTypes.ENUM('video', 'house'),
allowNull: false,
comment: '收藏的是视频还是房源项目',
},
relation_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
comment: '关联id',
},
user_id: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '用户编号',
},
state: {
type: DataTypes.INTEGER(1),
allowNull: false,
defaultValue: 1,
comment: '是1否0关注收藏(0-取消关注收藏)',
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
field: 'created_at',
get() {
const date = this.getDataValue('created_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'updated_at',
get() {
const date = this.getDataValue('updated_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
tableName: 'wafang_collection',
});
};
const moment = require('moment');
module.exports = app => {
const { DataTypes } = app.Sequelize;
return app.wafangModel.define('model', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
},
project_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
comment: '楼盘id',
},
image_id: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '中间库community_image编号id',
},
image_url: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '图片地址',
},
type: {
type: DataTypes.STRING(20),
allowNull: false,
comment: 'HUX:户型图,HD:活动图,XG:效果图,QW:区位图,PT:配套图,YB:样板间图,SJ:实景图,ZP:总平图,ZS:五证两书,ZJ:证件',
},
valid: {
type: DataTypes.INTEGER(1),
allowNull: false,
defaultValue: 1,
},
created_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'created_at',
get() {
const date = this.getDataValue('created_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'updated_at',
get() {
const date = this.getDataValue('updated_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
tableName: 'wafang_image',
});
};
const moment = require('moment');
module.exports = app => {
const { DataTypes } = app.Sequelize;
return app.wafangModel.define('model', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
},
type: {
type: DataTypes.ENUM('business', 'office'),
allowNull: false,
comment: '商用/办公开盘信息对应图片',
},
opening_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
comment: '开盘id',
},
image_url: {
type: DataTypes.STRING(255),
allowNull: false,
comment: '图片链接',
},
valid: {
type: DataTypes.INTEGER(1),
allowNull: false,
defaultValue: 1,
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
field: 'created_at',
get() {
const date = this.getDataValue('created_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'updated_at',
get() {
const date = this.getDataValue('updated_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
tableName: 'wafang_image_opening',
});
};
const moment = require('moment');
module.exports = app => {
const { DataTypes } = app.Sequelize;
return app.wafangModel.define('model', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
},
project_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
comment: '楼盘id',
},
layout_id: {
type: DataTypes.STRING(50),
allowNull: false,
},
layout_name: {
type: DataTypes.STRING(255),
allowNull: false,
comment: '户型名称',
},
area: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '户型面积',
},
image_url: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '户型图地址',
},
room_num: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '室',
},
hall_num: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '厅数量',
},
toilet_num: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '卫数量',
},
valid: {
type: DataTypes.INTEGER(1),
allowNull: false,
defaultValue: 1,
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
field: 'created_at',
get() {
const date = this.getDataValue('created_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
udpated_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'updated_at',
get() {
const date = this.getDataValue('updated_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
tableName: 'wafang_layout',
});
};
This diff is collapsed.
const moment = require('moment');
module.exports = app => {
const { DataTypes } = app.Sequelize;
const model = app.wafangModel.define('model', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
},
project_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
comment: '对应楼盘项目id',
},
presell_id: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '记录id',
},
status: {
type: DataTypes.ENUM('预售', '登记', '初核', '复核', '复核公示', '摇号', '选房', '已结束', ''),
allowNull: true,
defaultValue: '',
comment: '开盘状态',
},
license_date: {
type: DataTypes.DATE,
allowNull: true,
field: 'license_date',
get() {
const date = this.getDataValue('license_date');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
code: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '现/预售证号',
},
area: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '预售面积(登记面积)',
},
scope: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '销售范围(预售范围)',
},
ifpresel: {
type: DataTypes.STRING(30),
allowNull: true,
comment: '是否现房(是,否)',
},
fitment: {
type: DataTypes.INTEGER(1),
allowNull: true,
comment: '装修情况(是否精装)1/0',
},
list_price: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '本期均价/m²',
},
housenum: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '预售户数',
},
house_area_range: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '主力户型(户型区间)',
},
building_type: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '建筑类别(建筑形态)',
},
compelete_date: {
type: DataTypes.DATE,
allowNull: true,
field: 'compelete_date',
get() {
const date = this.getDataValue('compelete_date');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
total_num: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '房源数',
},
total_lottery: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '全部报名人数',
},
total_rate: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '全部中签率',
},
shed_renovation_num: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '棚改房源',
},
shed_renovation_lottery: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '棚改房源',
},
shed_renovation_rate: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '棚改中签率',
},
rigid_demand_num: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '刚需房源',
},
rigid_demand_lottery: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '刚需报名人数',
},
rigid_demand_rate: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '刚需中签率',
},
ordinary_num: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '普通房源',
},
ordinary_lottery: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '普通摇号人数',
},
ordinary_rate: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '普通中签率',
},
valid: {
type: DataTypes.INTEGER(1),
allowNull: true,
defaultValue: 1,
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
field: 'created_at',
get() {
const date = this.getDataValue('created_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'updated_at',
get() {
const date = this.getDataValue('updated_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
tableName: 'wafang_opening_house',
});
model.associate = function() {
app.wafangModel.WafangOpeningHouse.hasMany(app.wafangModel.WafangOpeningHouseStatus, { as: 'houseInfoStatus', sourceKey: 'id', foreignKey: 'opening_id' });
};
return model;
};
const moment = require('moment');
module.exports = app => {
const { DataTypes } = app.Sequelize;
return app.wafangModel.define('model', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
},
opening_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
comment: '开盘信息id',
},
status: {
type: DataTypes.ENUM('预售', '登记', '初核', '复核', '复核公示', '摇号', '选房', '已结束'),
allowNull: false,
defaultValue: '预售',
comment: '开盘状态',
},
start_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'start_at',
get() {
const date = this.getDataValue('start_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
end_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'end_at',
get() {
const date = this.getDataValue('end_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
valid: {
type: DataTypes.INTEGER(1),
allowNull: true,
defaultValue: 1,
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
field: 'created_at',
get() {
const date = this.getDataValue('created_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'updated_at',
get() {
const date = this.getDataValue('updated_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
tableName: 'wafang_opening_house_status',
});
};
const moment = require('moment');
module.exports = app => {
const { DataTypes } = app.Sequelize;
const model = app.wafangModel.define('model', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
},
project_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
comment: '对应楼盘项目id',
},
presell_id: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '记录id',
},
code: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '现/预售证号',
},
status: {
type: DataTypes.ENUM('预售/取证', '已结束'),
allowNull: false,
defaultValue: '预售/取证',
},
license_date: {
type: DataTypes.DATE,
allowNull: true,
field: 'license_date',
get() {
const date = this.getDataValue('license_date');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
soho_area: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: 'SOHO-建筑面积',
},
soho_housenum: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: 'SOHO-用户数',
},
soho_pro_management_fee: {
type: DataTypes.STRING(50),
allowNull: true,
comment: 'SOHO-物管费',
},
soho_floor_num: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: 'SOHO-楼层数',
},
soho_floor_height: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: 'SOHO-层高',
},
soho_area_range: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'SOHO-面积区间',
},
soho_ladder_household_proportion: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'SOHO-梯户比',
},
soho_ladder_speed: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'SOHO-电梯速度',
},
soho_share_rate: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'SOHO-公摊率',
},
soho_decoration: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'SOHO-装修情况',
},
soho_deliver_time: {
type: DataTypes.DATE,
allowNull: true,
field: 'soho_deliver_time',
get() {
const date = this.getDataValue('soho_deliver_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
soho_sale_price: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: 'SOHO-销售价格',
},
soho_sale_trends: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'SOHO-销售动态',
},
loft_area: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: 'LOFT-建筑面积',
},
loft_housenum: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: 'LOFT-用户数',
},
loft_pro_management_fee: {
type: DataTypes.STRING(50),
allowNull: true,
comment: 'LOFT-物管费',
},
loft_floor_num: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: 'LOFT-楼层数',
},
loft_floor_height: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: 'LOFT-层高',
},
loft_area_range: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'LOFT-面积区间',
},
loft_ladder_household_proportion: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'LOFT-梯户比',
},
loft_ladder_speed: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'LOFT-电梯速度',
},
loft_share_rate: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'LOFT-公摊率',
},
loft_decoration: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'LOFT-装修情况',
},
loft_deliver_time: {
type: DataTypes.DATE,
allowNull: true,
field: 'loft_deliver_time',
get() {
const date = this.getDataValue('loft_deliver_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
loft_sale_price: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: 'LOFT-销售价格',
},
loft_sale_trends: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'LOFT-销售动态',
},
office_area: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: 'OFFICE-建筑面积',
},
office_housenum: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: 'OFFICE-用户数',
},
office_pro_management_fee: {
type: DataTypes.STRING(50),
allowNull: true,
comment: 'OFFICE-物管费',
},
office_floor_num: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: 'OFFICE-楼层数',
},
office_floor_height: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: 'OFFICE-层高',
},
office_area_range: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'OFFICE-面积区间',
},
office_ladder_household_proportion: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'OFFICE-梯户比',
},
office_ladder_speed: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'OFFICE-电梯速度',
},
office_share_rate: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'OFFICE-公摊率',
},
office_decoration: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'OFFICE-装修情况',
},
office_deliver_time: {
type: DataTypes.DATE,
allowNull: true,
field: 'office_deliver_time',
get() {
const date = this.getDataValue('office_deliver_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
office_sale_price: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: 'OFFICE-销售价格',
},
office_sale_trends: {
type: DataTypes.STRING(255),
allowNull: true,
comment: 'OFFICE-销售动态',
},
valid: {
type: DataTypes.INTEGER(1),
allowNull: true,
defaultValue: 1,
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
field: 'created_at',
get() {
const date = this.getDataValue('created_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'updated_at',
get() {
const date = this.getDataValue('updated_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
tableName: 'wafang_opening_office',
});
model.associate = function() {
app.wafangModel.WafangOpeningOffice.hasMany(app.wafangModel.WafangImageOpening, { as: 'infoImage', sourceKey: 'id', foreignKey: 'opening_id' });
};
return model;
};
const moment = require('moment');
module.exports = app => {
const { DataTypes } = app.Sequelize;
const model = app.wafangModel.define('model', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
},
community_id: {
type: DataTypes.STRING(50),
allowNull: false,
},
name: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '分期名称',
},
type: {
type: DataTypes.ENUM('house', 'business', 'office'),
allowNull: false,
},
region_name: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '行政区划名称',
},
circle_name: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '环线名称',
},
address: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '分期地址',
},
alias: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '分期别名',
},
building_area: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '建筑面积',
},
b_x: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '百度经度',
},
b_y: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '百度维度',
},
cub_percent: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '容积率',
},
description: {
type: DataTypes.TEXT,
allowNull: true,
comment: '项目简介',
},
list_price: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '挂牌均价',
},
house_list_price: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '住宅挂牌均价',
},
business_list_price: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '商业挂牌均价',
},
office_list_price: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '办公挂牌均价',
},
park_count: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '车位数',
},
property_types: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '物业类型',
},
sale_address: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '售楼部地址',
},
sale_status: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '销售状态',
},
total_area: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '总面积',
},
vir_percent: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '绿化率',
},
developer: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '开发商名称',
},
manager: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '物业公司名称',
},
num_trade_30: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '近30天成交套数',
},
num_trade_total: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '累计售出套数',
},
num_for_sale: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '累计可售套数',
},
version: {
type: DataTypes.INTEGER(11),
allowNull: false,
},
type_building_area: {
type: DataTypes.DECIMAL,
allowNull: true,
},
cargo_lift_amount: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '货梯数',
},
type_cub_percent: {
type: DataTypes.DECIMAL,
allowNull: true,
},
field_area: {
type: DataTypes.DECIMAL,
allowNull: true,
},
house_amount: {
type: DataTypes.BIGINT,
allowNull: true,
comment: '总户数',
},
house_area_range_min: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '户型区间min',
},
house_area_range_max: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '户型区间max',
},
house_height: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '楼高',
},
type_list_price: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '挂牌均价',
},
marketing_dali: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '营销代理',
},
obtain_date: {
type: DataTypes.DATE,
allowNull: true,
field: 'obtain_date',
get() {
const date = this.getDataValue('obtain_date');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
parking_amount: {
type: DataTypes.BIGINT,
allowNull: true,
comment: '车位数',
},
parking_scale: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '车位比',
},
passenger_lift_amount: {
type: DataTypes.INTEGER(11),
allowNull: true,
comment: '货梯数',
},
pro_management_fee: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '物业费',
},
property_company: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '物业公司',
},
rights_years: {
type: DataTypes.BIGINT,
allowNull: true,
comment: '产权年限',
},
sell_date: {
type: DataTypes.DATE,
allowNull: true,
field: 'sell_date',
get() {
const date = this.getDataValue('sell_date');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
standard_area: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '楼栋对应标准层面积',
},
type_vir_percent: {
type: DataTypes.DECIMAL,
allowNull: true,
comment: '绿化率',
},
max_floor_distance: {
type: DataTypes.DECIMAL,
allowNull: true,
},
min_floor_distance: {
type: DataTypes.DECIMAL,
allowNull: true,
},
ladder_household_proportion: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '梯户比',
},
building_type: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '建筑形态',
},
is_hot: {
type: DataTypes.INTEGER(1),
allowNull: false,
defaultValue: 0,
comment: '是1否0热门楼盘',
},
is_partner: {
type: DataTypes.INTEGER(255),
allowNull: false,
defaultValue: 0,
comment: '是1否0合作楼盘',
},
show_trade: {
type: DataTypes.INTEGER(1),
allowNull: false,
defaultValue: 1,
comment: '是否展示交易数据',
},
order: {
type: DataTypes.INTEGER(255),
allowNull: false,
defaultValue: 0,
comment: '业态下推荐排序顺序默认0,第1-3位',
},
tags: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '标签数组json:["便宜","南北通透","有趣","后花园"]',
},
preview_image: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '列表展示预览图',
},
fitment: {
type: DataTypes.INTEGER(4),
allowNull: false,
defaultValue: 0,
comment: '装修情况,便于筛选(从presell同步)',
},
last_license_date: {
type: DataTypes.DATE,
allowNull: false,
field: 'last_license_date',
get() {
const date = this.getDataValue('last_license_date');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
status: {
type: DataTypes.ENUM('online', 'offline'),
allowNull: false,
defaultValue: 'online',
comment: '上下线',
},
valid: {
type: DataTypes.INTEGER(4),
allowNull: false,
defaultValue: 1,
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
field: 'created_at',
get() {
const date = this.getDataValue('created_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'updated_at',
get() {
const date = this.getDataValue('updated_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
tableName: 'wafang_project',
});
model.associate = function() {
app.wafangModel.WafangProject.hasMany(app.wafangModel.WafangOpeningHouse, { as: 'houseInfo', sourceKey: 'id', foreignKey: 'project_id' });
app.wafangModel.WafangProject.hasMany(app.wafangModel.WafangOpeningBusiness, { as: 'businessInfo', sourceKey: 'id', foreignKey: 'project_id' });
app.wafangModel.WafangProject.hasMany(app.wafangModel.WafangOpeningOffice, { as: 'officeInfo', sourceKey: 'id', foreignKey: 'project_id' });
app.wafangModel.WafangProject.hasMany(app.wafangModel.WafangProjectTrends, { as: 'projectTrends', sourceKey: 'id', foreignKey: 'project_id' });
app.wafangModel.WafangProject.hasMany(app.wafangModel.WafangLayout, { as: 'houseLayout', sourceKey: 'id', foreignKey: 'project_id' });
app.wafangModel.WafangProject.hasMany(app.wafangModel.WafangImage, { as: 'houseImage', sourceKey: 'id', foreignKey: 'project_id' });
};
return model;
};
const moment = require('moment');
module.exports = app => {
const { DataTypes } = app.Sequelize;
return app.wafangModel.define('model', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
},
project_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
comment: '对应楼盘项目id',
},
presell_id: {
type: DataTypes.STRING(50),
allowNull: true,
},
code: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '预售证编号',
},
trends_date: {
type: DataTypes.DATE,
allowNull: true,
field: 'trends_date',
get() {
const date = this.getDataValue('trends_date');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
status: {
type: DataTypes.STRING(50),
allowNull: true,
comment: '动态类型',
},
title: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '动态标题',
},
content: {
type: DataTypes.TEXT,
allowNull: true,
comment: '动态正文',
},
valid: {
type: DataTypes.INTEGER(1),
allowNull: false,
defaultValue: 1,
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
field: 'created_at',
get() {
const date = this.getDataValue('created_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'updated_at',
get() {
const date = this.getDataValue('updated_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
tableName: 'wafang_project_trends',
});
};
const moment = require('moment');
module.exports = app => {
const { DataTypes } = app.Sequelize;
return app.wafangModel.define('model', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
},
tag: {
type: DataTypes.STRING(255),
allowNull: false,
comment: '标签名称',
},
is_house: {
type: DataTypes.INTEGER(1),
allowNull: false,
defaultValue: 1,
comment: '是1否0是住宅标签',
},
is_business: {
type: DataTypes.INTEGER(1),
allowNull: false,
defaultValue: 1,
comment: '是否商业标签',
},
is_office: {
type: DataTypes.INTEGER(1),
allowNull: false,
defaultValue: 1,
comment: '是否办公标签',
},
status: {
type: DataTypes.ENUM('online', 'offline'),
allowNull: false,
defaultValue: 'online',
comment: '上下线',
},
valid: {
type: DataTypes.INTEGER(1),
allowNull: false,
defaultValue: 1,
},
created_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'created_at',
get() {
const date = this.getDataValue('created_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'updated_at',
get() {
const date = this.getDataValue('updated_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
tableName: 'wafang_tag',
});
};
const moment = require('moment');
module.exports = app => {
const { DataTypes } = app.Sequelize;
return app.wafangModel.define('model', {
id: {
type: DataTypes.STRING(50),
allowNull: false,
primaryKey: true,
comment: '用户编号',
},
openid: {
type: DataTypes.STRING(50),
allowNull: false,
comment: '用户openid',
unique: true,
},
phone: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '用户手机号加密',
},
gender: {
type: DataTypes.STRING(10),
allowNull: true,
comment: '性别',
},
avatar: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '头像',
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
field: 'created_at',
get() {
const date = this.getDataValue('created_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'updated_at',
get() {
const date = this.getDataValue('updated_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
tableName: 'wafang_user',
});
};
const moment = require('moment');
module.exports = app => {
const { DataTypes } = app.Sequelize;
const model = app.wafangModel.define('model', {
id: {
autoIncrement: true,
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
},
project_id: {
type: DataTypes.INTEGER(11),
allowNull: false,
comment: '对应楼盘项目id',
},
title: {
type: DataTypes.STRING(255),
allowNull: false,
comment: '标题',
},
description: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '描述',
},
cover_image: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '封面图地址',
},
url: {
type: DataTypes.STRING(255),
allowNull: true,
comment: '资源地址',
},
read_num: {
type: DataTypes.INTEGER(11),
allowNull: true,
defaultValue: 0,
comment: '观看数',
},
share_num: {
type: DataTypes.INTEGER(11),
allowNull: true,
defaultValue: 0,
comment: '分享数',
},
collection_num: {
type: DataTypes.INTEGER(11),
allowNull: false,
defaultValue: 0,
comment: '收藏数',
},
order: {
type: DataTypes.INTEGER(11),
allowNull: false,
defaultValue: 0,
comment: '排序(desc)',
},
status: {
type: DataTypes.ENUM('online', 'offline'),
allowNull: false,
defaultValue: 'offline',
comment: '上下线',
},
is_top: {
type: DataTypes.INTEGER(2),
allowNull: false,
defaultValue: 0,
comment: '是1否0置顶',
},
is_recommend: {
type: DataTypes.INTEGER(2),
allowNull: false,
defaultValue: 0,
comment: '是1否0首页推荐',
},
valid: {
type: DataTypes.INTEGER(1),
allowNull: false,
defaultValue: 1,
},
created_at: {
type: DataTypes.DATE,
allowNull: false,
field: 'created_at',
get() {
const date = this.getDataValue('created_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_at: {
type: DataTypes.DATE,
allowNull: true,
field: 'updated_at',
get() {
const date = this.getDataValue('updated_at');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
tableName: 'wafang_video',
});
model.associate = function() {
app.wafangModel.WafangVideo.belongsTo(app.wafangModel.WafangProject, { as: 'project', sourceKey: 'project_id', foreignKey: 'id' });
};
return model;
};
const Service = require('egg').Service;
const Excel = require('exceljs');
const Stream = require('stream');
const _ = require('lodash');
const contentDisposition = require('content-disposition');
const DATA = Symbol('excel_data');
const CALL = Symbol('excel_call');
class ExcelHelper {
async get(arg) {
return this._get(arg);
}
getObjectValue(obj, key) {
const keys = key.split('.');
let ret = obj;
for (let i = 0; i < keys.length; i++) {
if (ret !== undefined) {
ret = ret[keys[i]];
} else {
return undefined;
}
}
return ret;
}
}
class ExcelExportHelper extends ExcelHelper {
constructor(ctx) {
super();
this.ctx = ctx;
this.sheets = [];
this.fileName = '未命名' + (+new Date());
}
setFileName(fileName) {
this.fileName = fileName;
return this;
}
newSheet(data, sheetName = false) {
if (!sheetName) {
sheetName = 'sheet' + (this.sheets.length + 1);
}
this.sheets.push({
data,
sheetName,
columns: [],
});
return this;
}
newColumn(key, option) {
const lastSheet = this.sheets[this.sheets.length - 1];
const param = {};
if (lastSheet) {
if (typeof option === 'string') {
param.header = option;
} else {
param.header = option.title || key;
param.format = option.format;
}
}
param.key = key;
if (/\./.test(key)) {
if (!param.format) {
param.format = (val, i, row) => {
return this.getObjectValue(row, key);
};
} else {
const srcFormat = param.format;
param.format = (val, i, row) => {
return srcFormat(this.getObjectValue(row, key), row, i);
};
}
}
lastSheet.columns.push(param);
return this;
}
getFixedCellWidth(v) {
return Math.max(10, ('' + v).length * 2);
}
async _get() {
const stream = new Stream.PassThrough();
// const workbook = new Excel.Workbook();
const workbook = new Excel.stream.xlsx.WorkbookWriter({
stream,
useStyles: true,
useSharedStrings: true,
});
this.sheets.forEach(item => {
const worksheet = workbook.addWorksheet(item.sheetName);
const formatList = [];
if (item.data.length) {
const row = item.data[0];
if (item.columns.length === 0) {
for (const k in row) {
const v = row[k];
const type = typeof v;
if (type === 'string' || type === 'number') {
item.columns.push({
header: k,
key: k,
width: this.getFixedCellWidth(v),
});
}
}
} else {
for (let i = 0; i < item.columns.length; i++) {
const column = item.columns[i];
if (!column.width) {
column.width = this.getFixedCellWidth(row[column.key]);
}
if (column.format) {
formatList.push({
key: column.key,
fn: column.format,
});
}
}
}
}
worksheet.columns = item.columns;
for (let i = 0; i < item.data.length; i++) {
let row = item.data[i];
if (row instanceof Object) {
row = JSON.parse(JSON.stringify(row));
} else {
row = {};
}
for (let i = 0; i < formatList.length; i++) {
const item = formatList[i];
row[item.key] = item.fn(row[item.key], i, row);
}
worksheet.addRow(row).commit();
}
});
let { fileName } = this;
fileName += '.xlsx';
// fileName = encodeURIComponent(fileName);
// this.ctx.set('content-disposition', 'attachment;filename=' + fileName);
this.ctx.set('content-disposition', contentDisposition(fileName));
workbook.commit();
this.ctx.body = stream;
this.ctx.status = 200;
return stream;
}
}
class ExcelImportHelper extends ExcelHelper {
constructor(ctx) {
super();
this.ctx = ctx;
this.columns = [];
this[DATA] = false;
this.lineNumberKey = false;
}
showLineNumber(key) {
this.lineNumberKey = key;
return this;
}
newColumn(header, option) {
const param = {};
if (typeof option === 'string') {
param.key = option;
} else {
param.key = option.key || header;
param.format = option.format;
param.type = option.type;
param.required = option.required;
if (option.expect) {
const { expect } = option;
if (expect instanceof RegExp) {
param.expect = (val, rowNumber, colNumber) => {
if (!expect.test(val)) {
this.ctx.failed(`第${rowNumber}行第${colNumber}${header}格式不正确`);
}
};
} else if (expect instanceof Array) {
param.expect = (val, rowNumber, colNumber) => {
if (expect.indexOf(val) === -1) {
this.ctx.failed(`第${rowNumber}行第${colNumber}${header}只能为如下值:`
+ expect.join('、'));
}
};
}
}
}
param.header = header;
this.columns.push(param);
return this;
}
[CALL](i) {
const data = this[DATA][i];
const columns = [];
const ret = [];
if (data.length) {
const { values } = data[0];
for (let i = 0; i < this.columns.length; i++) {
const header = this.columns[i].header;
let find = false;
for (let j = 1; j < values.length; j++) {
let val = values[j];
if (val && val.richText) {
val = val.richText.map(item => item.text).join('');
}
if (val === header) {
const {
key,
format,
type,
expect,
required,
} = this.columns[i];
columns.push({
index: j,
key,
format,
type,
expect,
required,
});
find = true;
break;
}
}
if (!find) {
this.ctx.failed(`缺失列[${header}]`);
}
}
}
for (let i = 1; i < data.length; i++) {
const dict = {};
const { values, rowNumber } = data[i];
if (this.lineNumberKey) {
dict[this.lineNumberKey] = rowNumber;
}
for (let j = 0; j < columns.length; j++) {
const c = columns[j];
const colNumber = c.index;
let v = values[colNumber];
if (v instanceof Object) {
if ('text' in v) {
v = v.text;
} else if ('richText' in v) {
v = v.richText.map(item => item.text).join('');
} else if ('result' in v) {
v = v.result;
if (_.isNumber(v)) {
const tmp = Number(v.toFixed(4));
if (Math.abs(v - tmp) < 0.00000000001) {
v = tmp;
}
}
}
}
if (c.trim && _.isString(v)) {
v = _.trim(v);
}
if (v === undefined || v === '') {
if (c.required) {
this.ctx.failed(`第${rowNumber}行第${colNumber}列不能为空`);
}
}
switch (c.type) {
case 'int':
case 'integer':
if (!_.isInteger(v)) {
if (_.isString(v) && /^\s*\d+\s*$/.test(v)) {
v = +v;
} else if (v === undefined || v === '') {
v = null;
} else {
this.ctx.failed(`第${rowNumber}行第${colNumber}列不是整数`);
}
}
break;
case 'number':
if (!_.isNumber(v)) {
if (_.isString(v) && /^\s*\d+(?:\.\d+)?\s*$/.test(v)) {
v = +v;
} else if (v === undefined || v === '') {
v = null;
} else {
this.ctx.failed(`第${rowNumber}行第${colNumber}列不是数字`);
}
}
break;
case 'string':
if (!_.isString(v)) {
if (_.isNumber(v)) {
v = '' + v;
} else {
this.ctx.failed(`第${rowNumber}行第${colNumber}列不是字符串`);
}
}
break;
default:
break;
}
c.expect && c.expect(v, rowNumber, colNumber);
dict[c.key] = c.format ? c.format(v, rowNumber, colNumber) : v;
}
ret.push(dict);
}
this.lineNumberKey = false;
return ret;
}
async _get(stream) {
const sheetIndex = 0;
if (this[DATA]) {
return this[CALL](sheetIndex);
}
stream = stream || await this.ctx.getFileStream();
const workbook = new Excel.Workbook();
const data = [];
await workbook.xlsx.read(stream).then(workbook => {
workbook.eachSheet(function(worksheet) {
const sheetData = [];
worksheet.eachRow(function(row, rowNumber) {
sheetData.push({ rowNumber, values: row.values });
});
data.push(sheetData);
});
});
this[DATA] = data;
return this[CALL](sheetIndex);
}
}
class ExcelService extends Service {
newExport() {
return new ExcelExportHelper(this.ctx);
}
newImport() {
return new ExcelImportHelper(this.ctx);
}
}
module.exports = ExcelService;
const Service = require('egg').Service;
const _ = require('lodash');
class OutputService extends Service {
toArray(data, keys = [ '*' ]) {
const ret = [];
for (let i = 0; i < data.length; i++) {
const tmp = this.toObject(data[i], keys);
ret.push(tmp);
}
return ret;
}
toObject(data, keys = [ '*' ]) {
if (!(data instanceof Object)) {
return data;
}
const row = data.toJSON ? data.toJSON() : data;
const tmp = {};
for (let j = 0; j < keys.length; j++) {
const k = keys[j];
if (k === '*') {
for (const p in row) {
const v = row[p];
if (v instanceof Object) {
continue;
}
tmp[p] = v;
}
} else if (k instanceof Object) {
if (typeof k === 'function') {
k(tmp, row);
} else {
tmp[k.to] = _.get(row, k.from);
}
} else {
tmp[k] = row[k];
}
}
return tmp;
}
toTree(data, keys = [ '*' ], filter = () => false) {
/**
* 格式化成树
*/
const childrenDict = {};
const dataDict = {};
for (let i = 0; i < data.length; i++) {
const src = data[i];
if (filter(src)) {
continue;
}
const ele = this.toObject(src, keys);
dataDict[ele.id] = ele;
if (childrenDict[ele.id]) {
ele.children = childrenDict[ele.id];
}
if (!childrenDict[ele.parent_id]) {
const children = [];
childrenDict[ele.parent_id] = children;
if (dataDict[ele.parent_id]) {
dataDict[ele.parent_id].children = children;
}
}
childrenDict[ele.parent_id].push(ele);
}
return childrenDict[0] || [];
}
plattenArrayToTree(
list,
idStr = 'id',
parentIdStr = 'parent',
chindrenStr = 'children'
) {
/**
* 格式化成树 利用对象引用传递特点
*/
const tree = [],
hash = {},
id = idStr,
pid = parentIdStr,
children = chindrenStr;
for (let i = 0; i < list.length; i++) {
hash[list[i][id]] = list[i];
}
for (let j = 0; j < list.length; j++) {
const aVal = list[j];
const hashVP = hash[aVal[pid]];
if (hashVP) {
!hashVP[children] && (hashVP[children] = []);
hashVP[children].push(aVal);
} else {
tree.push(aVal);
}
}
return tree;
}
toObjectString(data) {
if (!(data instanceof Object)) {
return data;
}
const _this = this;
data = JSON.parse(JSON.stringify(data));
Object.keys(data).forEach(function(value) {
data[value] = _this.toDataString(data[value]);
});
return data;
}
toArrayString(data) {
if (!Array.isArray(data)) {
return data;
}
data = JSON.parse(JSON.stringify(data));
for (let i = 0; i < data.length; i++) {
const item = data[i];
if (typeof item === 'number') {
data[i] = String(item);
} else {
data[i] = this.toObjectString(item);
}
}
return data;
}
// 值转化为字符串
toDataString(data) {
if (Array.isArray(data)) {
data = this.toArrayString(data);
} else if (Object.prototype.toString.call(data) === '[object Object]') {
data = this.toObjectString(data);
} else {
if (data === null) {
data = '';
} else {
data = String(data);
}
}
return data;
}
}
module.exports = OutputService;
'use strict';
const Aes = require('nodejs-dd-aes');
const assert = require('assert');
module.exports = {
createDdEncrypt() {
const DD_CONFIG = this.app.config.DD_CONFIG;
assert(DD_CONFIG, 'DD_CONFIG required!');
assert(DD_CONFIG.aesKey, 'DD_CONFIG.aesKey required!');
assert(DD_CONFIG.corpId, 'DD_CONFIG.corpId required!');
assert(DD_CONFIG.token, 'DD_CONFIG.token required!');
const aes = new Aes(DD_CONFIG.aesKey, DD_CONFIG.corpId, DD_CONFIG.token);
return aes;
},
};
'use strict';
const ddUtils = require('./utils/dd');
const md5 = require('md5');
const assert = require('assert');
// TODO 从 cathay-portal-private 迁移过来,需要一点点替换
module.exports = app => {
const ddConfig = app.config.DD_CONFIG;
const agentIdConfig = ddConfig.agentId || {};
class Dd extends app.Service {
// constructor(ctx) {
// super(ctx);
// }
_getAgentId(type) {
let agentId;
if (typeof agentIdConfig === 'string') {
agentId = agentIdConfig;
} else {
agentId = agentIdConfig.default;
}
if (type) {
agentId = agentIdConfig[type];
}
if (!agentId) {
app.logger.error(`agentId not exist, agentIdType: ${type}`);
}
return agentId;
}
async getToken() {
let accessToken;
if (app.redis.get('zeus')) {
accessToken = await app.redis.get('zeus').get('corpAccessToken');
}
// 更新corpAccessToken
if (!accessToken) {
app.logger.info('[service:dd:getToken] start');
const result = await this.app.curl(`https://oapi.dingtalk.com/gettoken?corpid=${ddConfig.corpId}&corpsecret=${ddConfig.secret}`, {
dataType: 'json',
});
const resultData = result.data;
if (resultData.errcode) {
app.logger.error('[service:dd:getToken] error: ', resultData);
}
app.logger.info('[service:dd:getToken] end');
accessToken = resultData.access_token;
if (app.redis.get('zeus')) {
await app.redis.get('zeus').set('corpAccessToken', accessToken, 'ex', 5400);
}
}
return accessToken;
}
/** ******
* 企业会话消息接口
* https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7386797.0.0.rD6Zgg&treeId=172&articleId=104973&docType=1
* 消息类型及数据格式
* https://open-doc.dingtalk.com/doc2/detail.htm?spm=a219a.7629140.0.0.HTFncJ&treeId=172&articleId=104972&docType=1
* @param ddUserId
* @param data
* @return {boolean}
*/
async sendMessageByDdUserId(ddUserId, messageObj, agentIdType) {
if (Array.isArray(ddUserId)) {
ddUserId = ddUserId.join('|');
}
const token = await this.getToken();
app.logger.info('[service:dd:sendMessageByDdUserId] start, ddUserId:', ddUserId);
const result = await this.app.curl(`https://oapi.dingtalk.com/message/send?access_token=${token}`, {
method: 'POST',
contentType: 'json',
dataType: 'json',
data: Object.assign({
touser: `${ddUserId}`,
agentid: this._getAgentId(agentIdType),
}, messageObj),
});
const resultData = result.data;
if (resultData.errcode) {
app.logger.error('[service:dd:sendMessageByDdUserId] error: ', resultData);
}
app.logger.info('[service:dd:sendMessageByDdUserId] end');
return !resultData.errcode;
}
/** *******
*
* https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.qw2yFM&treeId=171&articleId=104910&docType=1
* https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.ZbDFk8&treeId=172&articleId=104966&docType=1
* TODO jsticket时间缓存优先
* @param originUrl
* @return {{token: *, signature: *, nonceStr: (string|string), timeStamp: number, corpId: (string|string), agentId: *}}
*/
async getJsApiConfig(originUrl, agentIdType) {
assert(originUrl, '[service:dd:getJsApiConfig] getJsApiConfig originUrl is required');
const agentId = this._getAgentId(agentIdType);
app.logger.info('[service:dd:getJsApiConfig] start: ', originUrl, agentId);
const REDIS_KEY = 'EMP_jsApiConfig' + md5(originUrl);
let jsApiConfig;
if (app.redis) {
jsApiConfig = await app.redis.get('zeus').get(REDIS_KEY);
}
if (!jsApiConfig) {
const token = await this.getToken();
const ticketResult = await this.app.curl(`https://oapi.dingtalk.com/get_jsapi_ticket?type=jsapi&access_token=${token}`, {
dataType: 'json',
});
const ticketResultData = ticketResult.data;
if (ticketResultData.errcode) {
app.logger.error('[service:dd:getJsApiConfig] Error', ticketResultData);
throw new Error(ticketResultData);
}
const ticket = ticketResultData.ticket;
app.logger.info('[service:dd:getJsApiConfig] TICKET: ', ticket);
const ticketTimeout = ticketResultData.expires_in - 200; // 默认expires_in = 7200s
const signedUrl = decodeURIComponent(originUrl);
const timeStamp = new Date().getTime();
const signature = ddUtils.sign({
nonceStr: ddConfig.nonceStr,
timeStamp,
url: signedUrl,
ticket,
});
jsApiConfig = {
token,
signature,
nonceStr: ddConfig.nonceStr,
timeStamp,
corpId: ddConfig.corpId,
agentId,
};
if (app.redis) {
await app.redis.get('zeus').set(REDIS_KEY, JSON.stringify(jsApiConfig), 'ex', ticketTimeout);
}
} else {
jsApiConfig = JSON.parse(jsApiConfig);
}
app.logger.info('[service:dd:getJsApiConfig] end: ', jsApiConfig);
return jsApiConfig;
}
/** **********
* https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.p1ESs4&treeId=172&articleId=104969&docType=1
* @param code
*/
async getUserInfo(code, userIdOnly = false) {
app.logger.info(`[service:dd:getUserInfo] start, code:${code}, userIdonly:${userIdOnly} `);
const token = await this.getToken();
const userIdResult = await this.app.curl(`https://oapi.dingtalk.com/user/getuserinfo?access_token=${token}&code=${code}`, {
dataType: 'json',
method: 'GET',
});
const userIdResultData = userIdResult.data;
if (userIdResultData.errcode) {
app.logger.error('[service:dd:getUserInfo] error get userid: ', userIdResultData);
throw new Error(userIdResultData);
}
const userId = userIdResult.data.userid;
app.logger.info(`[service:dd:getUserInfo] end WITH ONLY ID, ddUserId: ${userId}`);
if (userIdOnly) {
return userId;
}
const infoResult = await this.app.curl(`https://oapi.dingtalk.com/user/get?access_token=${token}&userid=${userId}`, {
dataType: 'json',
method: 'GET',
});
const infoResultData = infoResult.data;
if (infoResultData.errcode) {
app.logger.error('[service:dd:getUserInfo] error get userInfo', infoResultData);
throw new Error(infoResultData);
}
app.logger.info('[service:dd:getUserInfo] end:', infoResultData);
return infoResultData;
}
}
return Dd;
};
'use strict';
module.exports = app => {
class DdDepartment extends app.Service {
// constructor(ctx) {
// super(ctx);
// }
async getDepartmentFullPathByUser(ddUserId) {
this.app.logger.info(`[service:ddDepartment:getDepartmentFullPathByUser] start, ddUserId: ${ddUserId}`);
const token = await this.ctx.service.dd.getToken();
const result = await this.app.curl(`https://oapi.dingtalk.com/department/list_parent_depts?access_token=${token}&userId=${ddUserId}`, {
dataType: 'json',
method: 'GET',
});
const resultData = result.data;
if (resultData.errcode) {
this.app.logger.error('[service:ddDepartment:getDepartmentFullPathByUser] error: ', resultData);
throw new Error('[service:ddDepartment:getDepartmentFullPathByUser] error');
}
this.app.logger.info('[service:ddDepartment:getDepartmentFullPathByUser] end');
return resultData.department;
}
async getDepartmentFullPathByDepartment(departmentId) {
this.app.logger.info(`[service:ddDepartment:getDepartmentFullPathByDepartment] start, departmentId: ${departmentId}`);
const token = await this.ctx.service.dd.getToken();
const result = await this.app.curl(`https://oapi.dingtalk.com/department/list_parent_depts_by_dept?access_token=${token}&id=${departmentId}`, {
dataType: 'json',
method: 'GET',
});
const resultData = result.data;
if (resultData.errcode) {
this.app.logger.error('[service:ddDepartment:getDepartmentFullPathByDepartment] error: ', resultData);
throw new Error('[service:ddDepartment:getDepartmentFullPathByDepartment] error');
}
this.app.logger.info('[service:ddDepartment:getDepartmentFullPathByDepartment] end');
return resultData.parentIds;
}
async getDepartment(departmentId) {
this.app.logger.info(`[service:ddDepartment:getDepartment] start, departmentId: ${departmentId}`);
const token = await this.ctx.service.dd.getToken();
const result = await this.app.curl(`https://oapi.dingtalk.com/department/get?access_token=${token}&id=${departmentId}`, {
dataType: 'json',
method: 'GET',
});
const resultData = result.data;
if (resultData.errcode) {
this.app.logger.error('[service:ddDepartment:getDepartment] error: ', resultData);
throw new Error(JSON.stringify(resultData));
}
this.app.logger.info('[service:ddDepartment:getDepartment] end');
return resultData;
}
async getDepartments(parentId = '') {
this.app.logger.info(`[service:ddDepartment:getDepartments] start, parentId: ${parentId}`);
const token = await this.ctx.service.dd.getToken();
const result = await this.app.curl(`https://oapi.dingtalk.com/department/list?access_token=${token}&id=${parentId}`, {
dataType: 'json',
method: 'GET',
});
const resultData = result.data || {};
if (resultData.errcode) {
this.app.logger.error('[service:ddDepartment:getDepartments] error: ', resultData);
throw new Error(JSON.stringify(resultData));
}
const departments = resultData.department;
this.app.logger.info('[service:ddDepartment:getDepartments] end');
return departments;
}
async createDepartment(departmentInfo) {
this.app.logger.info('[service:ddDepartment:createDepartment] start, departmentInfo: ', departmentInfo);
const token = await this.ctx.service.dd.getToken();
const result = await this.app.curl(`https://oapi.dingtalk.com/department/create?access_token=${token}`, {
dataType: 'json',
contentType: 'json',
method: 'POST',
data: departmentInfo,
});
const resultData = result.data;
if (resultData.errcode) {
this.app.logger.error('[service:ddDepartment:createDepartment] error: ', resultData);
throw new Error(JSON.stringify(resultData));
}
this.app.logger.info('[service:ddDepartment:createDepartment] end');
return resultData.id;
}
/** ***********
* https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.W4WlHX&treeId=172&articleId=104979&docType=1#s3
* @param departmentInfo
*/
async updateDepartment(departmentInfo) {
this.app.logger.info('[service:ddDepartment:updateDepartment] start, updateDepartment: ', departmentInfo);
const token = await this.ctx.service.dd.getToken();
const result = await this.app.curl(`https://oapi.dingtalk.com/department/update?access_token=${token}`, {
dataType: 'json',
contentType: 'json',
method: 'POST',
data: departmentInfo,
});
const resultData = result.data;
if (resultData.errcode) {
this.app.logger.error('[service:ddDepartment:updateDepartment] error: ', resultData);
}
this.app.logger.info('[service:ddDepartment:updateDepartment] end');
return resultData;
}
async deleteDepartment(departmentId) {
const token = await this.ctx.service.dd.getToken();
this.app.logger.info('[service:ddDepartment:deleteDepartment] start, departmentId: ', departmentId);
const result = await this.app.curl(`https://oapi.dingtalk.com/department/delete?access_token=${token}&id=${departmentId}`, {
dataType: 'json',
method: 'GET',
});
const resultData = result.data;
if (resultData.errcode) {
this.app.logger.error('[service:ddDepartment:deleteDepartment] error: ', resultData);
}
this.app.logger.info('[service:ddDepartment:deleteDepartment] end');
return resultData;
}
}
return DdDepartment;
};
'use strict';
// const utility = require('utility');
module.exports = app => {
const DD_CONFIG = app.config.DD_CONFIG;
// https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.RIssni&treeId=385&articleId=104975&docType=1#s2
class DdEvents extends app.Service {
// constructor(ctx) {
// super(ctx);
// }
/**
*
* @param events
*/
async registerEvents(callbackUrl, events = [ 'user_add_org', 'user_modify_org', 'user_leave_org' ], isUpdate = false) {
this.app.logger.info('[service:ddEvents:registerEvents] start, isUpdate ', isUpdate);
const token = await this.ctx.service.dd.getToken();
let url = 'https://oapi.dingtalk.com/call_back/register_call_back';
if (isUpdate) {
url = 'https://oapi.dingtalk.com/call_back/update_call_back';
}
const result = await this.app.curl(`${url}?access_token=${token}`, {
dataType: 'json',
method: 'POST',
contentType: 'json',
data: {
call_back_tag: events,
token: DD_CONFIG.token,
aes_key: DD_CONFIG.aesKey,
url: callbackUrl || 'http://www.baidu.com',
},
});
const resultData = result.data;
if (resultData.errcode) {
this.app.logger.error('[service:ddEvents:registerEvents] error:', resultData);
}
this.app.logger.info('[service:ddEvents:registerEvents] end');
return !resultData.errcode;
}
async updateEvents(events) {
return await this.registerEvents(events, true);
}
async deleteEvents() {
this.app.logger.info('[service:ddEvents:deleteEvents] start');
const token = await this.ctx.service.dd.getToken();
const result = await this.app.curl(`https://oapi.dingtalk.com/call_back/delete_call_back?access_token=${token}`, {
dataType: 'json',
method: 'GET',
});
const resultData = result.data;
if (resultData.errcode) {
this.app.logger.error('[service:ddEvents:deleteEvents] error:', resultData);
throw new Error(JSON.stringify(resultData, null, 4));
}
this.app.logger.info('[service:ddEvents:deleteEvents] end');
return !resultData.errcode;
}
async queryEvents() {
this.app.logger.info('[service:ddEvents:queryEvents] start');
const token = await this.ctx.service.dd.getToken();
const result = await this.app.curl(`https://oapi.dingtalk.com/call_back/get_call_back?access_token=${token}`, {
dataType: 'json',
method: 'GET',
});
const resultData = result.data;
if (resultData.errcode) {
this.app.logger.error('[service:ddEvents:queryEvents] error:', resultData);
throw new Error(JSON.stringify(resultData, null, 4));
}
this.app.logger.info('[service:ddEvents:queryEvents] end');
return resultData;
}
}
return DdEvents;
};
'use strict';
const utility = require('utility');
module.exports = app => {
class DdProcess extends app.Service {
// constructor(ctx) {
// super(ctx);
// }
/** ********
* https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.rgrrdz&treeId=355&articleId=29498&docType=2
*/
async createProcess(options) {
const token = await this.ctx.service.dd.getToken();
let approvers = options.approvers;
let cc_list = options.cc_list;
if (Array.isArray(approvers)) {
approvers = approvers.join(',');
}
if (Array.isArray(cc_list)) {
cc_list = cc_list.join(',');
}
const finalData = Object.assign({
method: 'dingtalk.smartwork.bpms.processinstance.create',
session: token,
timestamp: utility.YYYYMMDDHHmmss(),
v: '2.0',
format: 'json',
simplify: true,
}, {
process_code: options.process_code,
originator_user_id: options.originator_user_id,
dept_id: options.dept_id,
approvers,
cc_list: cc_list || '',
cc_position: options.cc_position || 'START',
form_component_values: JSON.stringify(options.form_component_values),
});
this.app.logger.info('[service:ddProcess:createProcess] start, data:', finalData);
// 不需要设置 contentType,HttpClient 会默认以 application/x-www-form-urlencoded 格式发送请求
const result = await this.app.curl('https://eco.taobao.com/router/rest', {
dataType: 'json',
method: 'POST',
data: finalData,
});
let resultData = result.data;
if (resultData.error_response) {
this.app.logger.error('[service:ddProcess:createProcess] error1:', resultData);
throw new Error(JSON.stringify(resultData.error_response));
}
resultData = resultData.result;
if (resultData.ding_open_errcode || !resultData.is_success) {
this.app.logger.error('[service:ddProcess:createProcess] error2:', resultData);
throw new Error(JSON.stringify(resultData));
}
this.app.logger.info('[service:ddProcess:createProcess] end');
return resultData.process_instance_id;
}
/** ********
* https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.fNHF7j&treeId=355&articleId=29833&docType=2
*/
async listProcess(options) {
this.app.logger.info('[service:ddProcess:listProcess] start');
const token = await this.ctx.service.dd.getToken();
let startTime = new Date().getTime();
startTime -= 30 * 24 * 3600 * 1000;
const finalData = Object.assign({
method: 'dingtalk.smartwork.bpms.processinstance.list',
session: token,
timestamp: utility.YYYYMMDDHHmmss(),
v: '2.0',
format: 'json',
simplify: true,
}, {
process_code: options.process_code,
start_time: startTime,
});
// 不需要设置 contentType,HttpClient 会默认以 application/x-www-form-urlencoded 格式发送请求
const result = await this.app.curl('https://eco.taobao.com/router/rest', {
dataType: 'json',
method: 'POST',
data: finalData,
});
let resultData = result.data;
if (resultData.error_response) {
this.app.logger.error('[service:ddProcess:listProcess] error1:', resultData);
}
resultData = resultData.result;
if (resultData.ding_open_errcode) {
this.app.logger.error('[service:ddProcess:listProcess] error2:', resultData);
}
this.app.logger.info('[service:ddProcess:listProcess] end');
return resultData.result.list;
}
}
return DdProcess;
};
'use strict';
module.exports = app => {
const DD_CONFIG = app.config.DD_CONFIG;
const { appId, appSecret } = DD_CONFIG.sso || {};
/** *****
* 扫码appId , appSecret生成地址:http://open-dev.dingtalk.com/#/loginAndShareApp
* @type {string}
*/
class DdSns extends app.Service {
// constructor(ctx) {
// super(ctx);
// }
/** *****
* 获取钉钉开放应用的ACCESS_TOKEN
* https://open-doc.dingtalk.com/doc2/detail.htm?spm=a219a.7629140.0.0.WiN1vd&treeId=172&articleId=104968&docType=1#s0
* 貌似不支持缓存,文档写的不清楚
* @return {string}
*/
async getToken() {
app.logger.info('[service:ddSns:getToken] start');
const result = await this.app.curl(`https://oapi.dingtalk.com/sns/gettoken?appid=${appId}&appsecret=${appSecret}`, {
dataType: 'json',
});
const resultData = result.data;
if (resultData.errcode) {
app.logger.error('[service:ddSns:getToken] error:', resultData);
}
app.logger.info('[service:ddSns:getToken] end');
return resultData.access_token;
}
/** ******
* 获取用户授权的持久授权码:
* https://open-doc.dingtalk.com/doc2/detail.htm?spm=a219a.7629140.0.0.WiN1vd&treeId=172&articleId=104968&docType=1#s0
* @param tmpAuthCode
* @returns {*}
*/
async getPersistentCode(tmpAuthCode) {
const token = await this.getToken();
app.logger.info('[service:ddSns:getPersistentCode] start');
const result = await this.app.curl(`https://oapi.dingtalk.com/sns/get_persistent_code?access_token=${token}`, {
method: 'POST',
contentType: 'json',
dataType: 'json',
data: {
tmp_auth_code: `${tmpAuthCode}`,
},
});
const resultData = result.data;
if (resultData.errcode) {
app.logger.error(`[service:ddSns:getPersistentCode] error: ${resultData}`);
}
app.logger.info('[service:ddSns:getPersistentCode] end');
return resultData;
}
/** ***
* 获取用户授权的SNS_TOKEN
* https://open-doc.dingtalk.com/doc2/detail.htm?spm=a219a.7629140.0.0.WiN1vd&treeId=172&articleId=104968&docType=1#s0
* @param persistentCodeOptions
* @return {*}
*/
async getSnsToken(persistentCodeOptions) {
const token = await this.getToken();
app.logger.info('[service:ddSns:getSnsToken] start');
const result = await this.app.curl(`https://oapi.dingtalk.com/sns/get_sns_token?access_token=${token}`, {
method: 'POST',
contentType: 'json',
dataType: 'json',
data: {
openid: persistentCodeOptions.openid,
persistent_code: persistentCodeOptions.persistent_code,
},
});
const resultData = result.data;
if (resultData.errcode) {
app.logger.error('[service:ddSns:getSnsToken] error: ', resultData);
}
app.logger.info('[service:ddSns:getSnsToken] end');
return resultData.sns_token;
}
/** *****
* 获取用户授权的个人信息
* https://open-doc.dingtalk.com/doc2/detail.htm?spm=a219a.7629140.0.0.WiN1vd&treeId=172&articleId=104968&docType=1#s0
* @param snsToken
* @return {*}
*/
async getUserInfo(snsToken) {
app.logger.info('[service:ddSns:getUserInfo] start');
const result = await this.app.curl(`https://oapi.dingtalk.com/sns/getuserinfo?sns_token=${snsToken}`, {
dataType: 'json',
method: 'GET',
});
const resultData = result.data;
if (resultData.errcode) {
app.logger.error('[service:ddSns:getUserInfo] error: ', resultData);
}
app.logger.info('[service:ddSns:getUserInfo] end');
return resultData.user_info;
}
async getUserByPersistentCode(persistentCode) {
app.logger.info('[service:ddSns:getUserByPersistentCode] start');
const snsToken = await this.getSnsToken(persistentCode);
const userInfo = await this.getUserInfo(snsToken);
app.logger.info('[service:ddSns:getUserByPersistentCode] end');
return userInfo;
}
}
return DdSns;
};
'use strict';
const ddUtils = require('./utils/dd');
module.exports = app => {
class DdUser extends app.Service {
// constructor(ctx) {
// super(ctx);
// }
/**
* https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.6nS7Sz&treeId=172&articleId=104979&docType=1#s6
* @param userId
*/
async getUser(userId) {
this.app.logger.info('[service:ddUser:getUser] start, userId:', userId);
const token = await this.ctx.service.dd.getToken();
const result = await this.app.curl(`https://oapi.dingtalk.com/user/get?access_token=${token}&userid=${userId}`, {
dataType: 'json',
method: 'GET',
});
const resultData = result.data;
if (resultData.errcode) {
this.app.logger.error('[service:ddUser:getUser] getUser error:', resultData);
}
this.app.logger.info('[service:ddUser:getUser] end');
return resultData;
}
async getUsers(departmentId = '', casade = true) {
try {
let ddUsers = [];
this.app.logger.info(`[service:ddUser:getUsers] before getUsers, departmentId: ${departmentId}, casade: ${casade}`);
if (!casade) {
ddUsers = await this.getUsersByDepartment(departmentId);
} else {
let departments = await this.ctx.service.ddDepartment.getDepartments(departmentId);
departments = departments.map(department => {
return department.id;
});
if (departmentId) {
departments.push(departmentId);
}
for (const departmentId of departments) {
const subUsers = await this.getUsersByDepartment(departmentId);
ddUsers = ddUsers.concat(subUsers);
}
}
ddUsers = ddUtils.deleteRepeated(ddUsers);
this.app.logger.info('[service:ddUser:getUsers] end, ddUsers.length:', ddUsers.length);
return ddUsers;
} catch (e) {
this.ctx.app.logger.error('[service:ddUser:getUsers] error: ', e);
throw e;
}
}
async getUsersByDepartment(departmentId, offset = 0, size = 100) {
this.app.logger.info(`[service:ddUser:getUsersByDepartment] start, departmentId: ${departmentId}, offset: ${offset}`);
const token = await this.ctx.service.dd.getToken();
const result = await this.app.curl(`https://oapi.dingtalk.com/user/list?offset=${offset}&size=${size}&access_token=${token}&department_id=${departmentId}`, {
dataType: 'json',
method: 'GET',
});
const resultData = result.data;
if (resultData.errcode) {
this.ctx.app.logger.error('[service:ddUser:getUsersByDepartment] getUsersByDepartment', resultData);
}
let users = resultData.userlist || [];
if (resultData.hasMore) {
const more = await this.getUsersByDepartment(departmentId, offset + size, size);
users = users.concat(more);
}
this.app.logger.info('[service:ddUser:getUsersByDepartment] end');
return users;
}
/** ********
*
* https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.W4WlHX&treeId=172&articleId=104979&docType=1#s9
* @param userInfo
*/
async createUser(userInfo) {
this.app.logger.info('[service:ddUser:createUser] start:', userInfo);
const token = await this.ctx.service.dd.getToken();
const result = await this.app.curl(`https://oapi.dingtalk.com/user/create?access_token=${token}`, {
dataType: 'json',
contentType: 'json',
method: 'POST',
data: userInfo,
});
const resultData = result.data;
if (resultData.errcode) {
this.ctx.app.logger.error('[service:ddUser:createUser] createUser', resultData);
}
this.app.logger.info('[service:ddUser:createUser] end');
return resultData;
}
/** *************
* https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.W4WlHX&treeId=172&articleId=104979&docType=1#s9
* @param userInfo
*/
async updateUser(userInfo) {
this.app.logger.info('[service:ddUser:updateUser] start:', userInfo);
const token = await this.ctx.service.dd.getToken();
const result = await this.app.curl(`https://oapi.dingtalk.com/user/update?access_token=${token}`, {
dataType: 'json',
contentType: 'json',
method: 'POST',
data: userInfo,
});
const resultData = result.data;
if (resultData.errcode) {
this.app.logger.error('[service:ddUser:updateUser] error:', resultData);
}
this.app.logger.info('[service:ddUser:updateUser] end');
return resultData;
}
/** *************
*/
async getUseridByUnionid(unionid) {
this.app.logger.info('[service:ddUser:getUseridByUnionid] start:', unionid);
const token = await this.ctx.service.dd.getToken();
const result = await this.app.curl(`https://oapi.dingtalk.com/user/getUseridByUnionid?access_token=${token}&unionid=${unionid}`, {
dataType: 'json',
contentType: 'json',
method: 'GET',
});
const resultData = result.data;
if (resultData.errcode) {
this.app.logger.error('[service:ddUser:getUseridByUnionid] error:', resultData);
}
this.app.logger.info('[service:ddUser:getUseridByUnionid] end');
return resultData.userid;
}
/** **********
* https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.W4WlHX&treeId=172&articleId=104979&docType=1#s9
* @param userId
* @param times
* @param force
* @returns {boolean}
*/
async deleteUser(userId, times = 1) {
this.app.logger.info('[service:ddUser:deleteUser] start, userId:', userId);
const token = await this.ctx.service.dd.getToken();
const result = await this.app.curl(`https://oapi.dingtalk.com/user/delete?access_token=${token}&userid=${userId}`, {
dataType: 'json',
method: 'GET',
});
const resultData = result.data;
if (resultData.errcode) {
this.ctx.app.logger.error('[service:ddUser:deleteUser] error:', resultData, times);
if (times < 3) {
times++;
await this.deleteUser(userId, times);
} else {
throw new Error(`[service:ddUser:deleteUser] delete dd user error ${userId}, tried 3 times`);
}
}
this.app.logger.info('[service:ddUser:deleteUser] end');
return resultData;
}
}
return DdUser;
};
'use strict';
// const fs = require('fs');
const FormStream = require('formstream');
module.exports = app => {
/** ***********
* 所有多媒体文件相关接口参见:https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.6iqrBw&treeId=385&articleId=104971&docType=1
* @type {string}
*/
class DdMedia extends app.Service {
// constructor(ctx) {
// super(ctx);
// }
async upload(filePath = __filename) {
const token = await this.ctx.service.dd.getToken();
const form = new FormStream();
form.file('media', filePath);
app.logger.info('[service:ddMedia:upload] start');
const result = await this.app.curl(`https://oapi.dingtalk.com/media/upload?access_token=${token}&type=file`, {
dataType: 'json',
headers: form.headers(),
stream: form,
method: 'POST',
});
const resultData = result.data;
if (resultData.errcode) {
app.logger.error('[service:ddMedia:upload] error:', resultData);
throw new Error(resultData);
}
app.logger.info('[service:ddMedia:upload] end');
return resultData.media_id;
}
async get(mediaId) {
const token = await this.ctx.service.dd.getToken();
app.logger.info(`[service:ddMedia:get] start, mediaId: ${mediaId}`);
const result = await this.app.curl(`https://oapi.dingtalk.com/media/downloadFile?access_token=${token}&media_id=${mediaId}`, {
method: 'GET',
});
const resultData = result.data;
if (resultData.errcode) {
app.logger.error('[service:ddMedia:get] error:', resultData);
}
app.logger.info('[service:ddMedia:get] end');
return resultData;
}
}
return DdMedia;
};
'use strict';
const fs = require('fs');
const FormStream = require('formstream');
module.exports = app => {
/** *
* 所有盯盘相关接口参见:https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.8ltWef&treeId=385&articleId=104970&docType=1
* @type {string}
*/
class DdSpace extends app.Service {
// constructor(ctx) {
// super(ctx);
// }
async upload(filePath = __filename) {
app.logger.info(`[service:ddSpace:upload] start, filePath: ${filePath}`);
const token = await this.ctx.service.dd.getToken();
const agentId = this.ctx.service.dd._getAgentId();
const form = new FormStream();
const stateResult = fs.statSync(filePath);
const filesize = stateResult.size;
form.file('media', filePath, 'filename.txt', filesize);
const headers = form.headers();
const result = await this.app.curl(`https://oapi.dingtalk.com/file/upload/single?access_token=${token}&agent_id=${agentId}&file_size=${filesize}`, {
dataType: 'json',
headers,
stream: form,
method: 'POST',
});
const resultData = result.data;
if (resultData.errcode) {
app.logger.error('[service:ddSpace:upload] error: ', resultData);
}
app.logger.info('[service:ddSpace:upload] end');
return resultData.media_id;
}
async send(mediaId, userId, fileName) {
app.logger.info(`[service:ddSpace:send] start, mediaId: ${mediaId}, userId: ${userId}`);
const token = await this.ctx.service.dd.getToken();
const agentId = this.ctx.service.dd._getAgentId();
mediaId = encodeURIComponent(mediaId);
fileName = encodeURIComponent(fileName);
const result = await this.app.curl(`https://oapi.dingtalk.com/cspace/add_to_single_chat?access_token=${token}&agent_id=${agentId}&userid=${userId}&media_id=${mediaId}&file_name=${fileName}`, {
method: 'POST',
dataType: 'json',
});
const resultData = result.data;
if (resultData.errcode) {
app.logger.error('[service:ddSpace:send] error:', resultData);
}
app.logger.info('[service:ddSpace:send] end');
return true;
}
/** **
* 未测试
*/
async send2space(mediaId, userId, fileName) {
app.logger.info('[service:ddSpace:send2space] before send media to user to Dingding');
const token = await this.ctx.service.dd.getToken();
const agentId = this.ctx.service.dd._getAgentId();
mediaId = encodeURIComponent(mediaId);
fileName = encodeURIComponent(fileName);
const result = await this.app.curl(`https://oapi.dingtalk.com/cspace/add?access_token=${token}&agent_id=${agentId}&code=CODE&media_id=MEDIA_ID&space_id=SPACE_ID&folder_id=FOLDER_ID&name=NAME&overwrite=OVERWRITE`, {
method: 'POST',
});
const resultData = result.data;
if (resultData.errcode) {
app.logger.error('[service:ddSpace:send2space] send media to user error:', resultData);
}
app.logger.info('[service:ddSpace:send2space] end send media to user to Dingding');
return resultData;
}
}
return DdSpace;
};
/* eslint-disable array-callback-return */
'use strict';
const crypto = require('crypto');
const url = require('url');
module.exports = {
// getAgentId(type) {
// let agentId;
// if (typeof agentIdConfig === 'string') {
// agentId = agentIdConfig;
// } else {
// agentId = agentIdConfig.default;
// }
// if (type) {
// agentId = agentIdConfig[type];
// }
// return agentId;
// },
sign(params) {
const origUrl = params.url;
const origUrlObj = url.parse(origUrl);
delete origUrlObj.hash;
const newUrl = url.format(origUrlObj);
const plain = `jsapi_ticket=${params.ticket}&noncestr=${params.nonceStr}&timestamp=${params.timeStamp}&url=${newUrl}`;
const sha1 = crypto.createHash('sha1');
sha1.update(plain, 'utf8');
const signature = sha1.digest('hex');
return signature;
},
deleteRepeated(ddUsers) {
const map = {};
const deleted = [];
const result = ddUsers.filter(ddUser => {
if (!map[ddUser.userid]) {
map[ddUser.userid] = true;
return true;
}
deleted.push(ddUser);
});
return result;
},
};
'use strict';
module.exports.DD_CONFIG = {
corpId: 'dingdcf94075751f540635c2f4657eb6378f',
secret: 'C-uQKbuaA1zrne3ni2fwBfifMir9h4MEQTIrRi2LoQiE68LdxIWhBqnFxKLYABWT',
token: '123456', // 加解密时有用到
aesKey: '1234567890123456789012345678901234567890123', // 加解密时有用到
agentId: {
default: '116146340',
},
nonceStr: '123456',
sso: {
appId: 'dingoa9l870sdqembng3je',
appSecret: 'h0Y1uH4w4nkToIvzJzd6VKRNbJsqevOi791B0eeOVM87GrumW4xLEGOQqjzmo9eK',
},
};
'use strict';
const mock = require('egg-mock');
const assert = require('assert');
describe('extend/helper.js', () => {
let app;
let ctx;
before(function* () {
app = mock.app();
yield app.ready();
ctx = app.mockContext();
});
afterEach(mock.restore);
it('createDdEncrypt() encrypt string ', function* () {
const encrypt = ctx.helper.createDdEncrypt();
const result = encrypt.encode('success', 1500957302881, 'KOHjp9ss');
const jsonParsed = encrypt.decode(result.encrypt, result.timeStamp, result.nonce, result.msg_signature);
assert(jsonParsed === 'success');
});
it('createDdEncrypt() encrypt json ', function* () {
const encrypt = ctx.helper.createDdEncrypt();
const result = encrypt.encode('{"success": true}', 1500957302881, 'KOHjp9ss');
const jsonParsed = encrypt.decode(result.encrypt, result.timeStamp, result.nonce, result.msg_signature);
assert.deepEqual(jsonParsed, {
success: true,
});
});
});
'use strict';
const mock = require('egg-mock');
const assert = require('assert');
describe('service/Dd.js', () => {
let app;
let ctx;
before(function* () {
app = mock.app();
yield app.ready();
ctx = app.mockContext();
});
afterEach(mock.restore);
it('_getAgentId()', function* () {
const agentId = ctx.service.dd._getAgentId();
assert(agentId === '116146340');
});
it('getToken()', function* () {
const token = yield ctx.service.dd.getToken();
assert(token);
});
it('sendMessageByDdUserId()', function* () {
const result = yield ctx.service.dd.sendMessageByDdUserId('1012350541114092142', {
msgtype: 'text',
text: {
content: 'node dingding sdk sendMessageByDDUserId() unittest ' + Math.random(),
},
});
assert(result === true);
});
it('getJsApiConfig()', function* () {
const result = yield ctx.service.dd.getJsApiConfig(Math.random());
assert(result.token);
assert(result.signature);
assert(result.timeStamp);
assert(result.agentId);
});
it('getUserInfo()', function* () {
try {
yield ctx.service.dd.getUserInfo(Math.random());
assert(false);
} catch (e) {
assert(e);
}
});
});
'use strict';
const mock = require('egg-mock');
const assert = require('power-assert');
const mockjs = require('mockjs');
describe('service/DdDepartment.js', () => {
let app;
let ctx;
before(function* () {
app = mock.app();
yield app.ready();
ctx = app.mockContext();
});
after(mock.restore);
it('getDepartmentFullPathByUser()', function* () {
const result = yield ctx.service.ddDepartment.getDepartmentFullPathByUser('manager3882');
assert(result.length === 2 && result[0].length === 2 && result[1].length === 3);
});
it('getDepartmentFullPathByDepartment()', function* () {
const result = yield ctx.service.ddDepartment.getDepartmentFullPathByDepartment(50094428);
assert.deepEqual(result, [ 50094428, 45957469, 1 ]);
});
it('getDepartments()', function* () {
const result = yield ctx.service.ddDepartment.getDepartments();
assert(result.length > 3 && result[0].id === 1);
});
it('getDepartment(departmentId) check manager && group owner', function* () {
const result1 = yield ctx.service.ddDepartment.getDepartment(1);
assert(result1.name === 'NODE SDK');
assert(result1.orgDeptOwner === 'manager3882');
const result2 = yield ctx.service.ddDepartment.getDepartment(45957469);
assert(result2.deptManagerUseridList === 'manager3882');
});
it('create and delete department()', function* () {
const name = mockjs.Random.name();
const id = yield ctx.service.ddDepartment.createDepartment({
name,
parentid: 1,
});
assert(id);
const deleted = yield ctx.service.ddDepartment.deleteDepartment(id);
assert(deleted.errcode === 0);
});
it('updateDepartment()', function* () {
const result = yield ctx.service.ddDepartment.getDepartments();
const departmentId = result[1].id;
const name = mockjs.Random.name();
const up1 = yield ctx.service.ddDepartment.updateDepartment({
id: departmentId,
name,
});
assert(up1.errcode === 0);
const dep1 = yield ctx.service.ddDepartment.getDepartment(departmentId);
assert(dep1.name === name);
});
});
'use strict';
const mock = require('egg-mock');
// const assert = require('assert');
describe('service/DdEvents.js', () => {
let app;
let ctx;
beforeEach(function* () {
app = mock.app();
yield app.ready();
ctx = app.mockContext();
});
afterEach(mock.restore);
// it('registerEvents()', function* () {
// let isOk = yield ctx.service.ddEvents.registerEvents();
// assert(isOk);
// });
// it('queryEvents()', function* () {
// let isOk = yield ctx.service.ddEvents.queryEvents();
// assert(isOk);
// });
});
'use strict';
const mock = require('egg-mock');
const assert = require('assert');
describe('service/ddMedia.js', () => {
let app;
let ctx;
before(function* () {
app = mock.app();
yield app.ready();
ctx = app.mockContext();
});
afterEach(mock.restore);
it('upload and get()', function* () {
const mediaId = yield ctx.service.ddMedia.upload();
assert(mediaId);
const fileBuffer = yield ctx.service.ddMedia.get(mediaId);
assert(fileBuffer instanceof Buffer);
});
});
'use strict';
const mock = require('egg-mock');
const assert = require('assert');
describe('service/DdProcess.js', () => {
let app;
let ctx;
beforeEach(function* () {
app = mock.app();
yield app.ready();
ctx = app.mockContext();
});
afterEach(mock.restore);
it('createProcess()', function* () {
const processInstanceId = yield ctx.service.ddProcess.createProcess({
process_code: 'PROC-EF6YRO35P2-UXZMQYQNS8GZV2WMIMUV3-KXYSQQ5J-U',
originator_user_id: 'manager3882',
dept_id: '45957469',
approvers: [ 'manager3882', '0847412208841146' ],
// approvers: 'manager3882,0847412208841146',
// approvers: '012039633556498,043811683437365276',
form_component_values: [
{
name: '字段1',
value: '单测',
},
{
name: '字段2',
value: '审批流',
},
],
});
assert(processInstanceId);
});
it('listProcess()', function* () {
const result = yield ctx.service.ddProcess.listProcess({
process_code: 'PROC-EF6YRO35P2-UXZMQYQNS8GZV2WMIMUV3-KXYSQQ5J-U',
});
// console.log(JSON.stringify(result, null, 4))
assert(result.length);
});
});
'use strict';
const mock = require('egg-mock');
const assert = require('assert');
describe('service/DdSns.js', () => {
let app;
let ctx;
beforeEach(async function() {
app = mock.app();
await app.ready();
ctx = app.mockContext();
/** ********
* 此persistentCode需要从正式环境中获取
*/
app.mockService('ddSns', 'getPersistentCode', async function() {
return {
persistent_code: 'hjPNYHroltJKZAfwkApLoVkSDYftD9q00F5z5x40hZupptGmjeIaT3sOwfDHLC4a',
openid: '4XENP5LSW1UiE',
errmsg: 'ok',
unionid: 'ozQOkOek1sAiE',
errcode: 0,
};
});
});
afterEach(mock.restore);
it('getToken()', async function() {
const token = await ctx.service.ddSns.getToken();
assert(token && token.length === 32);
});
it('getPersistentCode()', async function() {
mock.restore();
const persistentCode = await ctx.service.ddSns.getPersistentCode('123');
assert(persistentCode.errcode === 40078);
});
return;
});
'use strict';
const mock = require('egg-mock');
const assert = require('assert');
describe('service/ddSpace.js', () => {
let app;
let ctx;
before(function* () {
app = mock.app();
yield app.ready();
ctx = app.mockContext();
});
afterEach(mock.restore);
it('upload and send()', function* () {
const mediaId = yield ctx.service.ddSpace.upload();
assert(mediaId && mediaId.indexOf('#') === 0);
const success = yield ctx.service.ddSpace.send(mediaId, 'manager3882', 'test.txt');
assert(success === true);
});
});
'use strict';
const mock = require('egg-mock');
const assert = require('assert');
const mockjs = require('mockjs');
describe('service/DdUser.js', () => {
let app;
let ctx;
beforeEach(function* () {
app = mock.app();
yield app.ready();
ctx = app.mockContext();
});
afterEach(mock.restore);
it('getUser()', function* () {
const result = yield ctx.service.ddUser.getUser('manager3882');
assert(result.name === '陈强');
});
it('createUser()', function* () {
const mobile = '186' + mockjs.Random.natural(10000000, 99999999);
const inserted = yield ctx.service.ddUser.createUser({
name: '张三',
mobile,
department: [ 1 ],
extattr: {
花名: '月光',
},
});
assert(inserted.errcode === 0);
const result = yield ctx.service.ddUser.getUser(inserted.userid);
assert(result.extattr['花名'] === '月光');
const deleted = yield ctx.service.ddUser.deleteUser(inserted.userid, 0, true);
assert(deleted.errcode === 0);
});
it('getUsers()', function* () {
const result = yield ctx.service.ddUser.getUsers();
assert(result.length > 2 && result[0].name);
});
// TODO AND TIPS: 只能更改未注册过的手机号码 2017-06-08 19:00:57,158 ERROR 13878 [ddUser] updateDdUser { errmsg: '更换的号码已注册过钉钉,无法使用该号码', errcode: 40021 }
// TODO AND TIPS: name有bug, 貌似在未激活时,更新会有随机错误,但extarr, position没有问题
it('updateUser()', function* () {
const mobile1 = '186' + mockjs.Random.natural(10000000, 90000000);
// let mobile2 = '186' + mockjs.Random.natural(80000000, 90000000);
const name1 = '张三';
const name2 = '李四';
const userid = mockjs.Random.natural();
const inserted = yield ctx.service.ddUser.createUser({
name: name1,
userid,
mobile: mobile1,
department: [ 1 ],
position: 'abc',
extattr: {
花名: '月光',
},
});
assert(inserted.errcode === 0);
const updated = yield ctx.service.ddUser.updateUser({
userid,
name: name2,
extattr: {
花名: '流水',
},
position: 'up',
});
assert(updated.errcode === 0);
const result = yield ctx.service.ddUser.getUser(userid);
assert(result.extattr['花名'] === '流水');
// assert(result['name'] === "李四");
assert(result.position === 'up');
const deleted = yield ctx.service.ddUser.deleteUser(inserted.userid, 0, true);
assert(deleted.errcode === 0);
});
});
'use strict';
const mailer = require('./lib/mailer');
module.exports = app => {
mailer(app);
};
'use strict';
/**
* egg-mailer default config
* @member Config#mailer
* @property {String} SOME_KEY - some description
*/
exports.mailer = {
};
'use strict';
const nodemailer = require('nodemailer');
const THANSPORTER = Symbol('egg-mailer.transporter');
class Mailer {
constructor(config, app) {
const {
host,
port,
user,
pass,
} = config;
this.sender = `"${config.sender}" <${config.user}>` || config.user;
this[THANSPORTER] = nodemailer.createTransport({
host,
port,
secure: port === 465, // true for 465, false for other ports
auth: {
user, // generated ethereal user
pass, // generated ethereal password
},
});
this.app = app;
}
send({ to, subject, text, html }) {
const { app } = this;
if (to instanceof Array) {
to = to.join(', ');
}
return new Promise((resolve, reject) => {
const startTime = +new Date();
this[THANSPORTER].sendMail({
from: this.sender,
to,
subject,
text,
html,
}, error => {
if (error) {
reject(error);
} else {
const ms = +new Date() - startTime;
app.logger.info(`[egg-mailer](${ms}ms) send <${subject}> to ${to}`);
resolve();
}
});
});
}
}
module.exports = app => {
app.beforeStart(async () => {
app.addSingleton('mailer', (config, app) => {
const mailer = new Mailer(config, app);
return mailer;
});
});
};
'use strict';
const Controller = require('egg').Controller;
class HomeController extends Controller {
async index() {
this.ctx.body = 'hi, ' + this.app.plugins.mailer.name;
}
}
module.exports = HomeController;
'use strict';
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
};
'use strict';
const mock = require('egg-mock');
describe('test/mailer.test.js', () => {
let app;
before(() => {
app = mock.app({
baseDir: 'apps/mailer-test',
});
return app.ready();
});
after(() => app.close());
afterEach(mock.restore);
it('should GET /', () => {
return app.httpRequest()
.get('/')
.expect('hi, mailer')
.expect(200);
});
});
'use strict';
const assert = require('assert');
const MIDDLEWARE_NAME = 'proxy';
module.exports = app => {
const { config } = app;
const index = config.appMiddleware.indexOf(MIDDLEWARE_NAME);
assert.equal(
index,
-1,
`Duplication of middleware name found: ${MIDDLEWARE_NAME}. Rename your middleware other than "${MIDDLEWARE_NAME}" please.`
);
config.appMiddleware.push(MIDDLEWARE_NAME);
};
'use strict';
module.exports = options => {
return async function httpProxy(ctx, next) {
const { proxyList = [] } = options;
const path = ctx.request.originalUrl || ctx.request.url;
for (let i = 0; i < proxyList.length; i++) {
const config = proxyList[i];
if (config.match.test(path)) {
await ctx.service.commom.proxy(config);
}
}
await next();
};
};
'use strict';
const { URL } = require('url');
const join = require('url').resolve;
const Service = require('egg').Service;
class LibService extends Service {
_resolve(path, options) {
let url = options.url;
if (url) {
if (!/^http/.test(url)) {
url = options.host ? join(options.host, url) : null;
}
return this._ignoreQuery(url);
}
if (typeof options.map === 'object') {
if (options.map && options.map[path]) {
path = this._ignoreQuery(options.map[path]);
}
} else if (typeof options.map === 'function') {
path = options.map(path);
}
return options.host ? join(options.host, path) : null;
}
_ignoreQuery(url) {
return url ? url.split('?')[0] : null;
}
async proxy(options) {
const { ctx } = this;
options || (options = {});
if (!(options.host || options.map || options.url)) {
throw new Error('miss options');
}
const path = ctx.request.originalUrl || ctx.request.url;
const url = this._resolve(path, options);
if (!url) return;
if (options.match) {
if (!path.match(options.match)) {
return;
}
}
const parsedBody = this._getParsedBody(ctx);
const opt = {
headers: ctx.header,
encoding: null,
method: ctx.method,
data: parsedBody,
dataType: 'text',
};
if (options.host) opt.headers.host = new URL(url).host;
for (const name in opt.headers) {
if (options.suppressRequestHeaders && options.suppressRequestHeaders.indexOf(name.toLowerCase()) >= 0) {
delete opt.headers[name];
}
}
const result = await ctx.curl(url, opt);
for (const name in result.headers) {
if (name === 'transfer-encoding') {
continue;
}
ctx.set(name, result.headers[name]);
}
ctx.body = result.data;
ctx.status = result.status;
ctx.logger.info(`proxy url:${url}`);
}
_getParsedBody(ctx) {
if (ctx.method === 'GET') return undefined;
let body = ctx.request.body;
if (body === undefined || body === null) {
return undefined;
}
const contentType = ctx.request.header['content-type'];
if (!Buffer.isBuffer(body) && typeof body !== 'string') {
if (contentType && contentType.indexOf('json') !== -1) {
body = JSON.stringify(body);
} else {
body = body + '';
}
}
return body;
}
}
module.exports = LibService;
'use strict';
/**
* egg-proxy default config
* @member Config#proxy
* @property {String} SOME_KEY - some description
*/
exports.proxy = {
match: /^\/assets/,
proxyList: [{
host: 'http://localhost:7001',
match: /^\/assets/,
}],
};
// This file is created by egg-ts-helper@1.25.8
// Do not modify this file!!!!!!!!!
import 'egg';
import ExportDefaultUser = require('../../../app/model/default/user');
import ExportWafangWafangAppointment = require('../../../app/model/wafang/wafangAppointment');
import ExportWafangWafangBlock = require('../../../app/model/wafang/wafangBlock');
import ExportWafangWafangBlockContent = require('../../../app/model/wafang/wafangBlockContent');
import ExportWafangWafangCollection = require('../../../app/model/wafang/wafangCollection');
import ExportWafangWafangImage = require('../../../app/model/wafang/wafangImage');
import ExportWafangWafangImageOpening = require('../../../app/model/wafang/wafangImageOpening');
import ExportWafangWafangLayout = require('../../../app/model/wafang/wafangLayout');
import ExportWafangWafangOpeningBusiness = require('../../../app/model/wafang/wafangOpeningBusiness');
import ExportWafangWafangOpeningHouse = require('../../../app/model/wafang/wafangOpeningHouse');
import ExportWafangWafangOpeningHouseStatus = require('../../../app/model/wafang/wafangOpeningHouseStatus');
import ExportWafangWafangOpeningOffice = require('../../../app/model/wafang/wafangOpeningOffice');
import ExportWafangWafangProject = require('../../../app/model/wafang/wafangProject');
import ExportWafangWafangProjectTrends = require('../../../app/model/wafang/wafangProjectTrends');
import ExportWafangWafangTag = require('../../../app/model/wafang/wafangTag');
import ExportWafangWafangUser = require('../../../app/model/wafang/wafangUser');
import ExportWafangWafangVideo = require('../../../app/model/wafang/wafangVideo');
declare module 'egg' {
interface IModel {
Default: {
User: ReturnType<typeof ExportDefaultUser>;
}
Wafang: {
WafangAppointment: ReturnType<typeof ExportWafangWafangAppointment>;
WafangBlock: ReturnType<typeof ExportWafangWafangBlock>;
WafangBlockContent: ReturnType<typeof ExportWafangWafangBlockContent>;
WafangCollection: ReturnType<typeof ExportWafangWafangCollection>;
WafangImage: ReturnType<typeof ExportWafangWafangImage>;
WafangImageOpening: ReturnType<typeof ExportWafangWafangImageOpening>;
WafangLayout: ReturnType<typeof ExportWafangWafangLayout>;
WafangOpeningBusiness: ReturnType<typeof ExportWafangWafangOpeningBusiness>;
WafangOpeningHouse: ReturnType<typeof ExportWafangWafangOpeningHouse>;
WafangOpeningHouseStatus: ReturnType<typeof ExportWafangWafangOpeningHouseStatus>;
WafangOpeningOffice: ReturnType<typeof ExportWafangWafangOpeningOffice>;
WafangProject: ReturnType<typeof ExportWafangWafangProject>;
WafangProjectTrends: ReturnType<typeof ExportWafangWafangProjectTrends>;
WafangTag: ReturnType<typeof ExportWafangWafangTag>;
WafangUser: ReturnType<typeof ExportWafangWafangUser>;
WafangVideo: ReturnType<typeof ExportWafangWafangVideo>;
}
}
}
......@@ -7,7 +7,9 @@ type AnyFunc<T = any> = (...args: any[]) => T;
type CanExportFunc = AnyFunc<Promise<any>> | AnyFunc<IterableIterator<any>>;
type AutoInstanceType<T, U = T extends CanExportFunc ? T : T extends AnyFunc ? ReturnType<T> : T> = U extends AnyClass ? InstanceType<U> : U;
import ExportTest from '../../../app/service/Test';
import ExportExcel = require('../../../app/service/excel');
import ExportFile from '../../../app/service/file';
import ExportOutput = require('../../../app/service/output');
import ExportMpEstateAppointment from '../../../app/service/mpEstate/appointment';
import ExportMpEstateList from '../../../app/service/mpEstate/list';
import ExportMpEstateMaterial from '../../../app/service/mpEstate/material';
......@@ -17,7 +19,9 @@ import ExportMpEstateVideo from '../../../app/service/mpEstate/video';
declare module 'egg' {
interface IService {
test: AutoInstanceType<typeof ExportTest>;
excel: AutoInstanceType<typeof ExportExcel>;
file: AutoInstanceType<typeof ExportFile>;
output: AutoInstanceType<typeof ExportOutput>;
mpEstate: {
appointment: AutoInstanceType<typeof ExportMpEstateAppointment>;
list: AutoInstanceType<typeof ExportMpEstateList>;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment