Commit 3ffae893 authored by 任国军's avatar 任国军

add course v3

parent c211713e
Pipeline #18027 passed with stage
in 56 seconds
...@@ -16,7 +16,6 @@ class UserController extends Controller { ...@@ -16,7 +16,6 @@ class UserController extends Controller {
// 请求微信授权 获取openid // 请求微信授权 获取openid
const wx_auth_ret = await ctx.service.course.v2.user.requestWxAuth(code); const wx_auth_ret = await ctx.service.course.v2.user.requestWxAuth(code);
const openid = wx_auth_ret.openid;// 获取openid const openid = wx_auth_ret.openid;// 获取openid
const session_key = wx_auth_ret.session_key;
// 检查是否已授权过 是否已入用户表 // 检查是否已授权过 是否已入用户表
let user = await ctx.classModel.V2.CourseUser.one({ where: { openid, is_deleted: 0 } }); let user = await ctx.classModel.V2.CourseUser.one({ where: { openid, is_deleted: 0 } });
...@@ -29,7 +28,7 @@ class UserController extends Controller { ...@@ -29,7 +28,7 @@ class UserController extends Controller {
// 存储缓存标识 // 存储缓存标识
const user_uuid = user.uuid; const user_uuid = user.uuid;
const key = 'course_v2_user_session_' + user_uuid; const key = 'course_v2_user_session_' + user_uuid;
await app.memcache.set(key, { user_uuid, openid, session_key }, 86400 * 7); await app.memcache.set(key, { user_uuid, openid }, 86400 * 7);
const auth_token = ctx.helper.md5(openid + user_uuid + 'jbwl'); const auth_token = ctx.helper.md5(openid + user_uuid + 'jbwl');
ctx.set('uuid', key); ctx.set('uuid', key);
ctx.set('auth_token', auth_token); ctx.set('auth_token', auth_token);
...@@ -41,15 +40,22 @@ class UserController extends Controller { ...@@ -41,15 +40,22 @@ class UserController extends Controller {
} }
async registerUserInfo() { async registerUserInfo() {
const { ctx, service } = this;
const { ctx } = this;
const uuid = ctx.userUuid;
const input_params = ctx.request.body; const input_params = ctx.request.body;
const { avatar, nickname, province, country, sex, city } = input_params; const { avatar, nickname, province, country, sex, city } = input_params;
const encryptedData = input_params.encryptedData || '';
const iv = input_params.iv || '';
const params = { avatar, nickname, province, country, sex, city, encryptedData, iv };
const ret = await service.course.v2.user.registerUserInfo(params); // 查找用户是否存在并更新
ctx.success(ret); const user = ctx.classModel.V2.CourseUser.one({ where: { uuid } });
await ctx.classModel.V2.CourseUser.edit({ params: { avatar, nickname, sex }, where: { uuid } });
let bindphone = 0;
if (user.phone) {
bindphone = 1;
}
const result = { bindphone };
ctx.success({ result });
} }
/** /**
...@@ -129,13 +135,7 @@ class UserController extends Controller { ...@@ -129,13 +135,7 @@ class UserController extends Controller {
ctx.success({ result }); ctx.success({ result });
} }
// 获取用户信息
async getUserInfo() {
const { ctx, service } = this;
const ret = await service.course.v2.user.getUserInfo();
ctx.success(ret);
}
} }
module.exports = UserController; module.exports = UserController;
...@@ -8,7 +8,7 @@ const request = require('request'); ...@@ -8,7 +8,7 @@ const request = require('request');
class WechatController extends Controller { class WechatController extends Controller {
async test() { async test() {
const { ctx, service } = this; const { ctx, service } = this;
const data = {MsgType: 'miniprogrampage'}; const data = { MsgType: 'miniprogrampage' };
await service.course.v2.wechat.sendMsg(data); await service.course.v2.wechat.sendMsg(data);
ctx.success(); ctx.success();
} }
......
'use strict';
const Controller = require('egg').Controller;
class InstitutionController extends Controller {
// 机构列表
async getInstitutions() {
const { ctx } = this;
const inputParams = ctx.request.query;
const ret = await ctx.service.course.v3.institution.getInstitutions(inputParams);
ctx.success(ret);
}
// 机构详情
async getInstitution() {
const { ctx } = this;
let inputParams = ctx.params;
const query = ctx.query;
inputParams = Object.assign(inputParams, query);
const result = await ctx.service.course.v3.institution.getInstitution(inputParams);
ctx.success({ result });
}
// 课程列表
async getClasses() {
const { ctx } = this;
const inputParams = ctx.request.query;
const ret = await ctx.service.course.v3.institution.getClasses(inputParams);
ctx.success(ret);
}
// 课程详情
async getClass() {
const { ctx } = this;
const class_id = ctx.params.class_id;
if (!class_id) {
ctx.failed('error class_id');
}
const ret = await ctx.service.course.v3.institution.getClass(class_id);
ctx.success(ret);
}
// 老师列表
async getTeachers() {
const { ctx } = this;
const inputParams = ctx.request.query;
if (ctx.isEmpty(inputParams.institution_id)) {
ctx.failed('institution_id is empty');
}
const ret = await ctx.service.course.v3.institution.getTeachers(inputParams);
ctx.success(ret);
}
// 老师详情
async getTeacher() {
const { ctx } = this;
const teacher_id = ctx.params.teacher_id;
if (!teacher_id) {
ctx.failed('error teacher_id');
}
const ret = await ctx.service.course.v3.institution.getTeacher(teacher_id);
ctx.success(ret);
}
// 获取分类
async getCats() {
const { ctx } = this;
const ret = await ctx.service.course.v3.institution.getCats();
ctx.success(ret);
}
// 搜索
async search() {
const { ctx } = this;
const inputParams = ctx.request.query;
const ret = await ctx.service.course.v3.institution.search(inputParams);
ctx.success(ret);
}
// 用户搜索历史
async getUserSearch() {
const { ctx, service } = this;
const ret = await service.course.v3.institution.getUserSearch();
ctx.success(ret);
}
// 获取热搜
async getHotSearch() {
const { ctx, service } = this;
const ret = await service.course.v3.institution.getHotSearch();
ctx.success(ret);
}
// 删除用户搜索历史
async deleteUserSearch() {
const { ctx, service } = this;
await service.course.v3.institution.deleteUserSearch();
ctx.success();
}
// 评论列表
async getComments() {
const { ctx, service } = this;
const inputParams = ctx.request.query;
const ret = await service.course.v3.institution.getComments(inputParams);
ctx.success(ret);
}
// 搜索联想
async getSuggestSearch() {
const { ctx, service } = this;
const inputParams = ctx.request.query;
const ret = await service.course.v3.institution.getSuggestSearch(inputParams);
ctx.success(ret);
}
// 用户收藏机构列表
async getUserCollectedInstitutions() {
const { ctx, service } = this;
const inputParams = ctx.request.query;
const ret = await service.course.v3.institution.getUserCollectedInstitutions(inputParams);
ctx.success(ret);
}
}
module.exports = InstitutionController;
'use strict';
const Controller = require('egg').Controller;
class LocationController extends Controller {
/**
* 筛选项
*/
async getAddress() {
const { ctx } = this;
let { lat, lng } = ctx.request.body;
let address = '';
if (lat && lng) {
const location_ret = await ctx.service.course.v3.lbs.getLBSLocation({ lat, lng });
const loaction = location_ret.result;
if (loaction.formatted_addresses && loaction.formatted_addresses.recommend) {
address = loaction.formatted_addresses.recommend;
lat = loaction.location.lat;
lng = loaction.location.lng;
}
}
if (!address) {
const ip_ret = await ctx.service.course.v3.lbs.getLBSIp();
const ip = ip_ret.result;
if (ip.location && ip.ad_info) {
const city = ip.ad_info.city ? ip.ad_info.city : '';
const district = ip.ad_info.district ? ip.ad_info.district : '';
address = ip.ad_info.province + city + district;
lat = ip.location.lat;
lng = ip.location.lng;
}
}
if (!address) {
address = '获取地理位置信息失败';
}
ctx.success({ result: { address, lat, lng } });
}
}
module.exports = LocationController;
'use strict';
const Controller = require('egg').Controller;
class OptionController extends Controller {
/**
* 筛选项
*/
async getOptions() {
const { ctx } = this;
const results = await ctx.service.course.v3.option.getOptions();
ctx.success({ results });
}
async getBanners() {
const { ctx, service } = this;
const inputParams = ctx.request.query;
if (ctx.isEmpty(inputParams.alias)) {
ctx.failed('alias is empty');
}
const ret = await service.course.v3.option.getBanners(inputParams.alias);
ctx.success(ret);
}
}
module.exports = OptionController;
'use strict';
const Controller = require('egg').Controller;
const uuidv4 = require('uuid/v4');
class UserController extends Controller {
async auth() {
const { ctx, app } = this;
const code = ctx.request.body.code;
if (!code) {
ctx.failed('error code');
}
// 请求微信授权 获取openid
const wx_auth_ret = await ctx.service.course.v3.user.requestWxAuth(code);
const openid = wx_auth_ret.openid;// 获取openid
const session_key = wx_auth_ret.session_key;
// 检查是否已授权过 是否已入用户表
let user = await ctx.classModel.V3.CourseUser.one({ where: { openid, is_deleted: 0 } });
if (!user || !user.uuid) {
const uuid = uuidv4();
user = await ctx.classModel.V3.CourseUser.create({ uuid, openid });
// user.uuid = uuid;
}
console.info(user);
// 存储缓存标识
const user_uuid = user.uuid;
const key = 'course_v2_user_session_' + user_uuid;
await app.memcache.set(key, { user_uuid, openid, session_key }, 86400 * 7);
const auth_token = ctx.helper.md5(openid + user_uuid + 'jbwl');
ctx.set('uuid', key);
ctx.set('auth_token', auth_token);
const result = { uuid: user_uuid, auth_token };
ctx.success({ result });
}
async registerUserInfo() {
const { ctx, service } = this;
const input_params = ctx.request.body;
const { avatar, nickname, province, country, sex, city } = input_params;
const encryptedData = input_params.encryptedData || '';
const iv = input_params.iv || '';
const params = { avatar, nickname, province, country, sex, city, encryptedData, iv };
const ret = await service.course.v3.user.registerUserInfo(params);
ctx.success(ret);
}
/**
* 获取baby信息
*/
async getBabyInfo() {
const { ctx } = this;
const result = await ctx.service.course.v3.user.getBabyInfo();
ctx.success({ result });
}
/**
* 保存baby信息
*/
async saveBabyInfo() {
const { ctx } = this;
const input_params = ctx.request.body;
const result = await ctx.service.course.v3.user.saveBabyInfo(input_params);
ctx.success({ result });
}
/**
* 删除baby信息
*/
async delBabyInfo() {
const { ctx } = this;
const result = await ctx.service.course.v3.user.delBabyInfo();
ctx.success({ result });
}
/**
* 用户收藏机构列表
*/
async getCollectInstitutions() {
const { ctx } = this;
let input_params = ctx.request.body;
input_params = Object.assign(input_params, ctx.request.query);
const result = await ctx.service.course.v3.user.getCollectInstitutions(input_params);
ctx.success({ result });
}
/**
* 收藏机构
*/
async collectInstitution() {
const { ctx } = this;
const institution_id = ctx.request.body.institution_id;
if (!institution_id) {
ctx.failed('error institution_id');
}
const result = await ctx.service.course.v3.user.collectInstitution(institution_id);
ctx.success({ result });
}
/**
* 取消收藏机构
*/
async delCollectInstitution() {
const { ctx } = this;
const institution_id = ctx.request.body.institution_id;
if (!institution_id) {
ctx.failed('error institution_id');
}
const result = await ctx.service.course.v3.user.delCollectInstitution(institution_id);
ctx.success({ result });
}
// 获取用户信息
async getUserInfo() {
const { ctx, service } = this;
const ret = await service.course.v3.user.getUserInfo();
ctx.success(ret);
}
}
module.exports = UserController;
'use strict';
const Controller = require('egg').Controller;
const crypto = require('crypto');
const fs = require('fs');
const request = require('request');
class WechatController extends Controller {
async test() {
const { ctx, service } = this;
const data = { MsgType: 'miniprogrampage' };
await service.course.v3.wechat.sendMsg(data);
ctx.success();
}
async callbackAction() {
const { ctx, service } = this;
await service.course.v3.wechat.callbackAction();
ctx.success('success');
}
async check() {
const { ctx } = this;
const params = ctx.request.query;
const {
signature,
timestamp,
nonce,
echostr,
} = params;
const array = [ '51gjj', timestamp, nonce ];
array.sort();
// 3.将三个参数字符串拼接成一个字符串进行sha1加密
const tempStr = array.join('');
const hashCode = crypto.createHash('sha1'); // 创建加密类型
const resultCode = hashCode.update(tempStr, 'utf8').digest('hex');
console.log(resultCode);
// 4.开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
if (resultCode === signature) {
ctx.success(echostr);
} else {
ctx.success();
}
}
}
module.exports = WechatController;
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, ENUM, TEXT } = app.Sequelize;
const CourseArticle = app.classModel.define('course_article', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
type: INTEGER,
cat_id: INTEGER,
title: STRING,
content: TEXT,
image: STRING,
source: STRING,
like_count: INTEGER,
sort: INTEGER,
status: ENUM('offline', 'online'),
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('updated_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_article',
});
return CourseArticle;
};
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, ENUM } = app.Sequelize;
const CourseBanner = app.classModel.define('course_banner', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
type_id: INTEGER,
title: STRING,
url: STRING,
link: STRING,
sort: INTEGER,
status: ENUM('offline', 'online'),
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('updated_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_banner',
});
return CourseBanner;
};
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, ENUM } = app.Sequelize;
const CourseBannerType = app.classModel.define('course_banner_type', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
title: STRING,
alias: STRING,
status: ENUM('offline', 'online'),
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('updated_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_banner_type',
});
return CourseBannerType;
};
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, ENUM } = app.Sequelize;
const CourseComment = app.classModel.define('course_comment', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
institution_id: INTEGER,
user_uuid: STRING,
nickname: STRING,
avatar: STRING,
content: STRING,
has_image: INTEGER,
status: ENUM('offline', 'online'),
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('updated_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_comment',
});
return CourseComment;
};
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, ENUM } = app.Sequelize;
const CourseInstitutionToTag = app.classModel.define('course_institution_to_tag', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
institution_id: INTEGER,
tag_id: INTEGER,
sort: INTEGER,
status: ENUM('offline', 'online'),
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('updated_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_institution_to_tag',
});
return CourseInstitutionToTag;
};
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, ENUM } = app.Sequelize;
const CourseLogUser = app.classModel.define('course_log_user', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
user_uuid: STRING,
type: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_log_user',
});
return CourseLogUser;
};
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, ENUM } = app.Sequelize;
const CourseSearch = app.classModel.define('course_search', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
content: STRING,
count: INTEGER,
is_hot: INTEGER,
sort: INTEGER,
status: ENUM('offline', 'online'),
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('updated_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_search',
});
return CourseSearch;
};
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, DECIMAL, TEXT, ENUM } = app.Sequelize;
const CourseStudentVideo = app.classModel.define('course_student_video', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
institution_id: INTEGER,
video_url: STRING,
title: STRING,
content: STRING,
age: STRING,
time: STRING,
sort: INTEGER,
cover_image: STRING,
status: ENUM('offline', 'online'),
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('updated_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_student_video',
});
return CourseStudentVideo;
};
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, ENUM } = app.Sequelize;
const CourseTag = app.classModel.define('course_tag', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: STRING,
sort: INTEGER,
status: ENUM('offline', 'online'),
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('updated_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_tag',
});
return CourseTag;
};
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, ENUM } = app.Sequelize;
const CourseUserSearch = app.classModel.define('course_user_search', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
user_uuid: STRING,
content: STRING,
status: ENUM('offline', 'online'),
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('updated_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_user_search',
});
return CourseUserSearch;
};
'use strict';
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, DECIMAL, TEXT, ENUM, FLOAT } = app.Sequelize;
const CourseArea = app.classModel.define('course_area', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true
},
institution_id: INTEGER,
name: STRING,
address: STRING,
phone: STRING,
lat: DECIMAL,
lng: DECIMAL,
status: ENUM('offline', 'online'),
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
}
}, {
timestamps: false,
tableName: 'course_area',
});
CourseArea.one = async (data) => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
return await CourseArea.findOne({
attributes: attributes,
where: where,
});
}
CourseArea.all = async (data) => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const order = data.order ? data.order : [];
return await CourseArea.findAll({
attributes: attributes,
where: where,
order,
});
}
CourseArea.list = async (data = {}) => {
const limit = data.limit ? Number(data.limit) : 10;
const page = data.page ? data.page : 1;
const order = data.order ? data.order : [];
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const condition = {
offset: (page - 1) * limit,
limit,
where: where,
order: order,
attributes: attributes,
};
const { count, rows } = await CourseArea.findAndCountAll(condition);
return { page, count, rows };
}
CourseArea.add = async (data) => {
try {
//返回promise对象实力 instance
const res = await CourseArea.create(data);
//从promise 实例中中获得需要的id号,id 必须是自增长,而且必须主键,否则返回null
return res.id;
} catch (error) {
throw (error);
}
}
CourseArea.edit = async (data) => {
const where = data.where;
const params = data.params;
try {
const res = await CourseArea.update(params, { where: where })
return res;
} catch (error) {
throw (error);
}
}
return CourseArea;
};
\ No newline at end of file
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, DECIMAL, TEXT, ENUM } = app.Sequelize;
const CourseCat = app.classModel.define('course_cat', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
parent_id: INTEGER,
name: STRING,
image: STRING,
active_image: STRING,
color: STRING,
tips: STRING,
status: ENUM('offline', 'online'),
is_deleted: INTEGER,
sort: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('updated_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_cat',
});
CourseCat.one = async data => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
return await CourseCat.findOne({
attributes,
where,
});
};
CourseCat.all = async data => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const order = data.order ? data.order : [];
return await CourseCat.findAll({
attributes,
where,
order,
});
};
CourseCat.list = async (data = {}) => {
const limit = data.limit ? Number(data.limit) : 10;
const page = data.page ? data.page : 1;
const order = data.order ? data.order : [];
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const condition = {
offset: (page - 1) * limit,
limit,
where,
order,
attributes,
};
const { count, rows } = await CourseCat.findAndCountAll(condition);
return { page, count, rows };
};
CourseCat.add = async data => {
try {
// 返回promise对象实力 instance
const res = await CourseCat.create(data);
// 从promise 实例中中获得需要的id号,id 必须是自增长,而且必须主键,否则返回null
return res.id;
} catch (error) {
throw (error);
}
};
CourseCat.edit = async data => {
const where = data.where;
const params = data.params;
try {
const res = await CourseCat.update(params, { where });
return res;
} catch (error) {
throw (error);
}
};
return CourseCat;
};
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, DECIMAL, TEXT, ENUM } = app.Sequelize;
const CourseClass = app.classModel.define('course_class', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true
},
institution_id: INTEGER,
name: STRING,
// image: STRING,
price: DECIMAL,
min_age: INTEGER,
max_age: INTEGER,
suit_base: STRING,
type: STRING,
class_system: STRING,
class_period: STRING,
class_time: STRING,
student_count: STRING,
point: STRING,
description: STRING,
status: ENUM('offline', 'online'),
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
}
}, {
timestamps: false,
tableName: 'course_class',
});
CourseClass.one = async (data) => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
return await CourseClass.findOne({
attributes: attributes,
where: where,
});
}
CourseClass.all = async (data) => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const order = data.order ? data.order : [];
return await CourseClass.findAll({
attributes: attributes,
where: where,
order,
});
}
CourseClass.list = async (data = {}) => {
const limit = data.limit ? Number(data.limit) : 10;
const page = data.page ? data.page : 1;
const order = data.order ? data.order : [];
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const condition = {
offset: (page - 1) * limit,
limit,
where: where,
order: order,
attributes: attributes,
};
const { count, rows } = await CourseClass.findAndCountAll(condition);
return { page, count, rows };
}
CourseClass.add = async (data) => {
try {
//返回promise对象实力 instance
const res = await CourseClass.create(data);
//从promise 实例中中获得需要的id号,id 必须是自增长,而且必须主键,否则返回null
return res.id;
} catch (error) {
throw (error);
}
}
CourseClass.edit = async (data) => {
const where = data.where;
const params = data.params;
try {
const res = await CourseClass.update(params, { where: where })
return res;
} catch (error) {
throw (error);
}
}
return CourseClass;
};
\ No newline at end of file
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, DECIMAL, TEXT, ENUM } = app.Sequelize;
const CourseImages = app.classModel.define('course_images', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
type: INTEGER,
type_id: INTEGER,
image_url: STRING,
video_url: STRING,
is_cover: INTEGER,
is_video: INTEGER,
sort: INTEGER,
status: ENUM('offline', 'online'),
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_images',
});
CourseImages.one = async data => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
return await CourseImages.findOne({
attributes,
where,
});
};
CourseImages.all = async data => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const order = data.order ? data.order : [];
return await CourseImages.findAll({
attributes,
where,
order,
});
};
CourseImages.list = async (data = {}) => {
const limit = data.limit ? Number(data.limit) : 10;
const page = data.page ? data.page : 1;
const order = data.order ? data.order : [];
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const condition = {
offset: (page - 1) * limit,
limit,
where,
order,
attributes,
};
const { count, rows } = await CourseImages.findAndCountAll(condition);
return { page, count, rows };
};
CourseImages.add = async data => {
try {
// 返回promise对象实力 instance
const res = await CourseImages.create(data);
// 从promise 实例中中获得需要的id号,id 必须是自增长,而且必须主键,否则返回null
return res.id;
} catch (error) {
throw (error);
}
};
CourseImages.edit = async data => {
const where = data.where;
const params = data.params;
try {
const res = await CourseImages.update(params, { where });
return res;
} catch (error) {
throw (error);
}
};
return CourseImages;
};
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, DECIMAL, TEXT, ENUM } = app.Sequelize;
const CourseArea = app.classModel.define('course_area');
const CourseInstitution = app.classModel.define('course_institution', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: STRING,
type: STRING,
open_time: STRING,
establishment_time: STRING,
class_type: STRING,
teacher_count: INTEGER,
teacher_experience: INTEGER,
logo: STRING,
corner: STRING,
min_age: INTEGER,
max_age: INTEGER,
price: STRING,
characteristic: STRING,
description: TEXT,
honor: STRING,
point: STRING,
status: ENUM('offline', 'online'),
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_institution',
});
CourseInstitution.hasMany(CourseArea, {
foreignKey: {
name: 'institution_id',
allowNull: false,
},
});
CourseInstitution.one = async data => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
return await CourseInstitution.findOne({
attributes,
where,
});
};
CourseInstitution.all = async data => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const order = data.order ? data.order : [];
return await CourseInstitution.findAll({
attributes,
where,
order,
});
};
CourseInstitution.list = async (data = {}) => {
const limit = data.limit ? Number(data.limit) : 10;
const page = data.page ? data.page : 1;
const order = data.order ? data.order : [];
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const condition = {
offset: (page - 1) * limit,
limit,
where,
order,
attributes,
};
const { count, rows } = await CourseInstitution.findAndCountAll(condition);
return { page, count, rows };
};
CourseInstitution.add = async data => {
try {
// 返回promise对象实力 instance
const res = await CourseInstitution.create(data);
// 从promise 实例中中获得需要的id号,id 必须是自增长,而且必须主键,否则返回null
return res.id;
} catch (error) {
throw (error);
}
};
CourseInstitution.edit = async data => {
const where = data.where;
const params = data.params;
try {
const res = await CourseInstitution.update(params, { where });
return res;
} catch (error) {
throw (error);
}
};
return CourseInstitution;
};
'use strict';
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, DECIMAL, TEXT, ENUM } = app.Sequelize;
const CourseInstitutionToCat = app.classModel.define('course_institution_to_cat', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true
},
institution_id: INTEGER,
cat_id: INTEGER,
status: ENUM('offline', 'online'),
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('updated_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_institution_to_cat',
});
CourseInstitutionToCat.one = async (data) => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
return await CourseInstitutionToCat.findOne({
attributes: attributes,
where: where,
});
}
CourseInstitutionToCat.all = async (data) => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const order = data.order ? data.order : [];
return await CourseInstitutionToCat.findAll({
attributes: attributes,
where: where,
order,
});
}
CourseInstitutionToCat.list = async (data = {}) => {
const limit = data.limit ? Number(data.limit) : 10;
const page = data.page ? data.page : 1;
const order = data.order ? data.order : [];
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const condition = {
offset: (page - 1) * limit,
limit,
where: where,
order: order,
attributes: attributes,
};
const { count, rows } = await CourseInstitutionToCat.findAndCountAll(condition);
return { page, count, rows };
}
CourseInstitutionToCat.add = async (data) => {
try {
//返回promise对象实力 instance
const res = await CourseInstitutionToCat.create(data);
//从promise 实例中中获得需要的id号,id 必须是自增长,而且必须主键,否则返回null
return res.id;
} catch (error) {
throw (error);
}
}
CourseInstitutionToCat.edit = async (data) => {
const where = data.where;
const params = data.params;
try {
const res = await CourseInstitutionToCat.update(params, { where: where })
return res;
} catch (error) {
throw (error);
}
}
return CourseInstitutionToCat;
};
\ No newline at end of file
'use strict';
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, DECIMAL, TEXT, ENUM } = app.Sequelize;
const CourseLogUserGps = app.classModel.define('course_log_user_gps', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true
},
user_uuid: STRING,
address: STRING,
lat: DECIMAL,
lng: DECIMAL,
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('updated_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_log_user_gps',
});
CourseLogUserGps.one = async (data) => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
return await CourseLogUserGps.findOne({
attributes: attributes,
where: where,
});
}
CourseLogUserGps.all = async (data) => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const order = data.order ? data.order : [];
return await CourseLogUserGps.findAll({
attributes: attributes,
where: where,
order,
});
}
CourseLogUserGps.list = async (data = {}) => {
const limit = data.limit ? Number(data.limit) : 10;
const page = data.page ? data.page : 1;
const order = data.order ? data.order : [];
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const condition = {
offset: (page - 1) * limit,
limit,
where: where,
order: order,
attributes: attributes,
};
const { count, rows } = await CourseLogUserGps.findAndCountAll(condition);
return { page, count, rows };
}
CourseLogUserGps.add = async (data) => {
try {
//返回promise对象实力 instance
const res = await CourseLogUserGps.create(data);
//从promise 实例中中获得需要的id号,id 必须是自增长,而且必须主键,否则返回null
return res.id;
} catch (error) {
throw (error);
}
}
CourseLogUserGps.edit = async (data) => {
const where = data.where;
const params = data.params;
try {
const res = await CourseLogUserGps.update(params, { where: where })
return res;
} catch (error) {
throw (error);
}
}
return CourseLogUserGps;
};
\ No newline at end of file
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, DECIMAL, TEXT, ENUM } = app.Sequelize;
const CourseTeacher = app.classModel.define('course_teacher', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true
},
institution_id: INTEGER,
name: STRING,
avatar: STRING,
teacher_experience: INTEGER,
nationality: STRING,
educational_background: STRING,
certificate: STRING,
honor: STRING,
lesson: STRING,
work_experience: STRING,
point: STRING,
description: TEXT,
status: ENUM('offline', 'online'),
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
}
}, {
timestamps: false,
tableName: 'course_teacher',
});
CourseTeacher.one = async (data) => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
return await CourseTeacher.findOne({
attributes: attributes,
where: where,
});
}
CourseTeacher.all = async (data) => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const order = data.order ? data.order : [];
return await CourseTeacher.findAll({
attributes: attributes,
where: where,
order,
});
}
CourseTeacher.list = async (data = {}) => {
const limit = data.limit ? Number(data.limit) : 10;
const page = data.page ? Number(data.page) : 1;
const order = data.order ? data.order : [];
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const condition = {
offset: (page - 1) * limit,
limit,
where: where,
order: order,
attributes: attributes,
};
const { count, rows } = await CourseTeacher.findAndCountAll(condition);
return { page, count, rows };
}
CourseTeacher.add = async (data) => {
try {
//返回promise对象实力 instance
const res = await CourseTeacher.create(data);
//从promise 实例中中获得需要的id号,id 必须是自增长,而且必须主键,否则返回null
return res.id;
} catch (error) {
throw (error);
}
}
CourseTeacher.edit = async (data) => {
const where = data.where;
const params = data.params;
try {
const res = await CourseTeacher.update(params, { where: where })
return res;
} catch (error) {
throw (error);
}
}
return CourseTeacher;
};
\ No newline at end of file
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, DECIMAL, TEXT, ENUM } = app.Sequelize;
const CourseUser = app.classModel.define('course_user', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
uuid: STRING,
app_id: STRING,
app_user_id: STRING,
app_type_id: STRING,
user_id: STRING,
phone: STRING,
nickname: STRING,
avatar: STRING,
sex: STRING,
openid: STRING,
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('updated_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_user',
});
CourseUser.one = async data => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
return await CourseUser.findOne({
attributes,
where,
});
};
CourseUser.all = async data => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const order = data.order ? data.order : [];
return await CourseUser.findAll({
attributes,
where,
order,
});
};
CourseUser.list = async (data = {}) => {
const limit = data.limit ? Number(data.limit) : 10;
const page = data.page ? data.page : 1;
const order = data.order ? data.order : [];
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const condition = {
offset: (page - 1) * limit,
limit,
where,
order,
attributes,
};
const { count, rows } = await CourseUser.findAndCountAll(condition);
return { page, count, rows };
};
CourseUser.add = async data => {
try {
// 返回promise对象实力 instance
const res = await CourseUser.create(data);
// 从promise 实例中中获得需要的id号,id 必须是自增长,而且必须主键,否则返回null
return res.id;
} catch (error) {
throw (error);
}
};
CourseUser.edit = async data => {
const where = data.where;
const params = data.params;
try {
const res = await CourseUser.update(params, { where });
return res;
} catch (error) {
throw (error);
}
};
return CourseUser;
};
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, DECIMAL, TEXT, ENUM } = app.Sequelize;
const CourseUserBaby = app.classModel.define('course_user_baby', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
user_uuid: STRING,
gender: ENUM('boy', 'girl'),
birth: STRING,
address: STRING,
lat: DECIMAL,
lng: DECIMAL,
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('updated_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_user_baby',
});
CourseUserBaby.one = async data => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
return await CourseUserBaby.findOne({
attributes,
where,
});
};
CourseUserBaby.all = async data => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const order = data.order ? data.order : [];
return await CourseUserBaby.findAll({
attributes,
where,
order,
});
};
CourseUserBaby.list = async (data = {}) => {
const limit = data.limit ? Number(data.limit) : 10;
const page = data.page ? data.page : 1;
const order = data.order ? data.order : [];
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const condition = {
offset: (page - 1) * limit,
limit,
where,
order,
attributes,
};
const { count, rows } = await CourseUserBaby.findAndCountAll(condition);
return { page, count, rows };
};
CourseUserBaby.add = async data => {
try {
// 返回promise对象实力 instance
const res = await CourseUserBaby.create(data);
// 从promise 实例中中获得需要的id号,id 必须是自增长,而且必须主键,否则返回null
return res.id;
} catch (error) {
throw (error);
}
};
CourseUserBaby.edit = async data => {
const where = data.where;
const params = data.params;
try {
const res = await CourseUserBaby.update(params, { where });
return res;
} catch (error) {
throw (error);
}
};
return CourseUserBaby;
};
'use strict';
const moment = require('moment');
module.exports = app => {
const { STRING, INTEGER, DATE, DECIMAL, TEXT, ENUM } = app.Sequelize;
const CourseUserCollection = app.classModel.define('course_user_collection', {
id: {
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
user_uuid: STRING,
institution_id: INTEGER,
type: INTEGER,
type_id: INTEGER,
is_deleted: INTEGER,
created_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('created_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
updated_time: {
type: DATE,
allowNull: true,
get() {
const date = this.getDataValue('updated_time');
return date ? moment(date).format('YYYY-MM-DD HH:mm:ss') : undefined;
},
},
}, {
timestamps: false,
tableName: 'course_user_collection',
});
CourseUserCollection.one = async data => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
return await CourseUserCollection.findOne({
attributes,
where,
});
};
CourseUserCollection.all = async data => {
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const order = data.order ? data.order : [];
return await CourseUserCollection.findAll({
attributes,
where,
order,
});
};
CourseUserCollection.list = async (data = {}) => {
const limit = data.limit ? Number(data.limit) : 10;
const page = data.page ? data.page : 1;
const order = data.order ? data.order : [];
const attributes = data.attributes ? data.attributes : {};
const where = data.where ? data.where : {};
const condition = {
offset: (page - 1) * limit,
limit,
where,
order,
attributes,
};
const { count, rows } = await CourseUserCollection.findAndCountAll(condition);
return { page, count, rows };
};
CourseUserCollection.add = async data => {
try {
// 返回promise对象实力 instance
const res = await CourseUserCollection.create(data);
// 从promise 实例中中获得需要的id号,id 必须是自增长,而且必须主键,否则返回null
return res.id;
} catch (error) {
throw (error);
}
};
CourseUserCollection.edit = async data => {
const where = data.where;
const params = data.params;
try {
const res = await CourseUserCollection.update(params, { where });
return res;
} catch (error) {
throw (error);
}
};
return CourseUserCollection;
};
...@@ -19,7 +19,6 @@ module.exports = app => { ...@@ -19,7 +19,6 @@ module.exports = app => {
router.post('third', '/user/auth', 'course.v2.user.auth');// 微信授权登录 router.post('third', '/user/auth', 'course.v2.user.auth');// 微信授权登录
router.post('third', '/user/register_user', miniAuth, 'course.v2.user.registerUserInfo');// 授权后注册用户 router.post('third', '/user/register_user', miniAuth, 'course.v2.user.registerUserInfo');// 授权后注册用户
router.get('third', '/user/info', miniAuth, 'course.v2.user.getUserInfo');// 获取用户信息
router.get('third', '/user/baby', miniAuth, 'course.v2.user.getBabyInfo');// 获取baby信息 router.get('third', '/user/baby', miniAuth, 'course.v2.user.getBabyInfo');// 获取baby信息
router.post('third', '/user/baby', miniAuth, 'course.v2.user.saveBabyInfo');// 保存baby信息 router.post('third', '/user/baby', miniAuth, 'course.v2.user.saveBabyInfo');// 保存baby信息
......
'use strict';
module.exports = app => {
const router = app.router.namespace(app.config.projectRootPath + '/course/v3');
const loginAuth = app.middleware.loginAuth({ type: 'new' });// 登录中间件
const miniAuth = app.middleware.miniAuthV2();// 因为不跟现有的用户中心系统,所以使用单独的登录中间件
// 版本二
router.get('third', '/cats', 'course.v3.institution.getCats');// 分类
router.get('third', '/options', 'course.v3.option.getOptions');// 筛选项
router.get('third', '/banners', 'course.v3.option.getBanners');// banner
router.post('third', '/address', miniAuth, 'course.v3.location.getAddress');// 根据经纬度或ip获取地理位置信息
router.get('third', '/institutions', miniAuth, 'course.v3.institution.getInstitutions');// 机构列表
router.get('third', '/institution/:institution_id', miniAuth, 'course.v3.institution.getInstitution');// 机构详情
router.get('third', '/classes', miniAuth, 'course.v3.institution.getClasses');// 课程列表
router.get('third', '/class/:class_id', miniAuth, 'course.v3.institution.getClass');// 课程详情
router.get('third', '/teachers', miniAuth, 'course.v3.institution.getTeachers');// 老师列表
router.get('third', '/teacher/:teacher_id', miniAuth, 'course.v3.institution.getTeacher');// 老师详情
router.post('third', '/user/auth', 'course.v3.user.auth');// 微信授权登录
router.post('third', '/user/register_user', miniAuth, 'course.v3.user.registerUserInfo');// 授权后注册用户
router.get('third', '/user/info', miniAuth, 'course.v3.user.getUserInfo');// 获取用户信息
router.get('third', '/user/baby', miniAuth, 'course.v3.user.getBabyInfo');// 获取baby信息
router.post('third', '/user/baby', miniAuth, 'course.v3.user.saveBabyInfo');// 保存baby信息
router.delete('third', '/user/baby', miniAuth, 'course.v3.user.delBabyInfo');// 删除baby信息
router.get('third', '/user/collection/institution', miniAuth, 'course.v3.institution.getUserCollectedInstitutions');// 收藏的机构列表
router.post('third', '/user/collection/institution', miniAuth, 'course.v3.user.collectInstitution');// 收藏机构
router.delete('third', '/user/collection/institution', miniAuth, 'course.v3.user.delCollectInstitution');// 取消收藏机构
router.get('third', '/wechat/callbackAction', 'course.v3.wechat.check');
router.post('third', '/wechat/callbackAction', 'course.v3.wechat.callbackAction');
router.post('third', '/wechat/test', 'course.v3.wechat.test');
router.get('third', '/comments', miniAuth, 'course.v3.institution.getComments');// 评论列表
router.get('third', '/search/hot', miniAuth, 'course.v3.institution.getHotSearch');// 热搜
router.get('third', '/search/suggest', miniAuth, 'course.v3.institution.getSuggestSearch');// 搜索联想
router.get('third', '/search', miniAuth, 'course.v3.institution.search');// 搜索
router.get('third', '/user/search', miniAuth, 'course.v3.institution.getUserSearch');// 用户搜索历史
router.delete('third', '/user/search', miniAuth, 'course.v3.institution.deleteUserSearch');// 清空用户搜索记录
};
...@@ -118,59 +118,7 @@ class UserService extends Service { ...@@ -118,59 +118,7 @@ class UserService extends Service {
} }
// 获取用户信息
async getUserInfo() {
const { ctx } = this;
const userUuid = ctx.userUuid;
const userInfo = await ctx.classModel.V2.CourseUser.findOne({ where: { uuid: userUuid, is_deleted: 0 } });
if (ctx.isEmpty(userInfo)) {
ctx.failed('用户不存在');
}
const ret = {
bind_phone: ctx.isEmpty(userInfo.phone) ? 0 : 1,
};
return ret;
}
// 保存用户信息
async registerUserInfo(input) {
const { ctx } = this;
const userUuid = ctx.userUuid;
const { avatar, nickname, province, country, sex, city, encryptedData, iv } = input;
// 查找用户是否存在并更新
const user = await ctx.classModel.V2.CourseUser.findOne({ where: { uuid: userUuid, is_deleted: 0 } });
if (ctx.isEmpty(user)) {
ctx.failed('用户不存在');
}
const data = {};
let bind_phone = ctx.isEmpty(user.phone) ? 0 : 1;
if (!ctx.isEmpty(avatar)) {
data.avatar = avatar;
}
if (!ctx.isEmpty(nickname)) {
data.nickname = nickname;
}
if (!ctx.isEmpty(encryptedData) && !ctx.isEmpty(iv)) {
const decoded = await ctx.service.course.v2.wechat.decodeData(encryptedData, iv);
if (!ctx.isEmpty(decoded) && !ctx.isEmpty(decoded.phoneNumber)) {
data.phone = decoded.phoneNumber;
bind_phone = 1;
}
}
await ctx.classModel.V2.CourseUser.update(data, { where: { id: user.id } });
const ret = {
result: {
bind_phone,
},
};
return ret;
}
} }
module.exports = UserService; module.exports = UserService;
...@@ -4,7 +4,6 @@ const APPID = 'wx07a5f0ed5bdf4751'; ...@@ -4,7 +4,6 @@ const APPID = 'wx07a5f0ed5bdf4751';
const SECRET = 'a1b2d32b018988176181497bd74a0b7d'; const SECRET = 'a1b2d32b018988176181497bd74a0b7d';
const fs = require('fs'); const fs = require('fs');
const request = require('request'); const request = require('request');
const crypto = require('crypto');
class WechatService extends Service { class WechatService extends Service {
async getAccessToken() { async getAccessToken() {
...@@ -137,38 +136,6 @@ class WechatService extends Service { ...@@ -137,38 +136,6 @@ class WechatService extends Service {
return ret; return ret;
} }
// 解密
async decodeData(encryptedData, iv) {
const { ctx } = this;
const userUuid = ctx.userUuid;
const userInfo = await ctx.app.memcache.get('course_v2_user_session_' + userUuid);
if (ctx.isEmpty(userInfo) || ctx.isEmpty(userInfo.session_key)) {
ctx.failed('sessionKey不存在');
}
const sessionKey = new Buffer(userInfo.session_key, 'base64');
// base64 decode
encryptedData = new Buffer(encryptedData, 'base64');
iv = new Buffer(iv, 'base64');
try {
// 解密
const decipher = crypto.createDecipheriv('aes-128-cbc', sessionKey, iv);
// 设置自动 padding 为 true,删除填充补位
decipher.setAutoPadding(true);
var decoded = decipher.update(encryptedData, 'binary', 'utf8');
decoded += decipher.final('utf8');
decoded = JSON.parse(decoded);
} catch (err) {
ctx.failed('Illegal Buffer');
}
return decoded;
}
} }
module.exports = WechatService; module.exports = WechatService;
'use strict';
const Service = require('egg').Service;
const R = require('ramda');
const _ = require('lodash');
const moment = require('moment');
class InstitutionSubService extends Service {
// 获取分类
async getCats() {
const { ctx } = this;
const AllCats = await ctx.classModel.V3.CourseCat.findAll({ where: { status: 1, is_deleted: 0 } });
const rootCats = [];
let ret = [];
// 先取出一级分类
for (const v of AllCats) {
if (v.parent_id === 0) {
rootCats[v.id] = v.dataValues;
rootCats[v.id].child = [{ id: 0, value: v.id, name: '全部', parent_id: 0, image: 'http://r.51gjj.com/webpublic/images/20191118/s6yRUsc5kclyu.png', active_image: 'http://r.51gjj.com/webpublic/images/20191118/zYHkYp85vxk5m.png', color: '', tips: '', status: 'online', is_deleted: 0 }];
}
}
// 放入二级分类
for (const v of AllCats) {
if (v.parent_id > 0) {
v.value = v.id;
rootCats[v.parent_id].child.push(v);
}
}
const sort = function(a, b) {
return a.sort - b.sort;
};
// 整理
for (const v of rootCats) {
if (!ctx.isEmpty(v)) {
v.child = R.sort(sort)(v.child);
ret.push(v);
}
}
ret = R.sort(sort)(ret);
return { results: ret };
}
// 获取机构详情
async getInstitution(input) {
const { ctx } = this;
const userUuid = ctx.userUuid;
const { institution_id } = input;
const lat = ctx.isEmpty(input.lat) ? 0 : input.lat;
const lng = ctx.isEmpty(input.lng) ? 0 : input.lng;
let institution = await ctx.classModel.V3.CourseInstitution.findOne({ where: { id: institution_id, status: 'online', is_deleted: 0 } });
if (ctx.isEmpty(institution)) {
ctx.failed('机构不存在');
}
institution = institution.dataValues;
// 顶部相册
const images = await ctx.classModel.V3.CourseImages.findAll({ where: { type: 1, type_id: institution_id, status: 'online', is_deleted: 0 } });
// 去重
const checkList = [];
let album = [];
for (const v of images) {
if ((v.is_video === 0 && checkList.includes(v.image_url)) || (v.is_video === 1 && checkList.includes(v.video_url))) {
continue;
}
album.push(v);
}
// 排序,视频在前
const albumSort = function(a, b) {
if (a.is_video === b.is_video) {
return a.sort - b.sort;
}
return b.is_video - a.is_video;
};
album = R.sort(albumSort)(album);
// 学习成果
const student_video = await ctx.classModel.V3.CourseStudentVideo.findAll({ where: { institution_id, status: 'online', is_deleted: 0 }, order: [[ 'sort', 'asc' ]] });
// 教师
const teachers = await ctx.classModel.V3.CourseTeacher.findAll({ where: { institution_id, status: 'online', is_deleted: 0 }, raw: true });
// 课程
const classes = await ctx.classModel.V3.CourseClass.findAll({ where: { institution_id, status: 'online', is_deleted: 0 }, raw: true });
// 处理课程封面图
const classHandle = [];
if (classes.length > 0) {
for (const v of classes) {
classHandle.push(ctx.classModel.V3.CourseImages.findOne({ where: { type: 2, type_id: v.id, status: 'online', is_deleted: 0 }, order: [[ 'sort', 'asc' ]] }));
}
const classImages = await Promise.all(classHandle).then(result => {
return result;
}).catch(error => {
ctx.failed(error);
});
for (const i in classes) {
classes[i].image = classImages[i];
}
}
// 校区
let areas = await ctx.classModel.V3.CourseArea.findAll({ where: { institution_id, status: 'online', is_deleted: 0 }, raw: true });
const areaHandle = [];
if (areas.length > 0) {
for (const v of areas) {
areaHandle.push(this.formatArea(v, { lng, lat }));
}
areas = await Promise.all(areaHandle).then(result => {
return result;
}).catch(error => {
ctx.failed(error);
});
}
areas = _.orderBy(areas, [ 'distance' ], [ 'asc' ]);
// 是否已收藏
const userCollect = await ctx.classModel.V3.CourseUserCollection.findOne({ where: { institution_id, user_uuid: userUuid, is_deleted: 0 } });
// 获取所有标签
const allTags = await ctx.classModel.V3.CourseTag.findAll({ where: { status: 'online', is_deleted: 0 } });
const tagList = [];
for (const v of allTags) {
tagList[v.id] = v;
}
let tags = await ctx.classModel.V3.CourseInstitutionToTag.findAll({ where: { institution_id: institution.id, status: 'online', is_deleted: 0 } });
tags = _.orderBy(tags, [ 'sort' ], [ 'asc' ]);
institution.tags = [];
for (const v of tags) {
institution.tags.push(tagList[v.tag_id]);
}
institution.phone = areas.length > 0 ? areas[0].phone : '';
institution.distance = areas.length > 0 ? String(areas[0].distance) + 'km' : '无法计算';
institution.travel_method = areas.length > 0 ? areas[0].travel_method : '';
institution.travel_tips = areas.length > 0 ? areas[0].travel_tips : '';
const ret = {
album,
detail: institution,
student_video,
teachers,
classes,
areas,
is_collected: ctx.isEmpty(userCollect) ? 0 : 1,
};
return ret;
}
// 处理校区
async formatArea(area, location) {
const { ctx, service } = this;
if (ctx.isEmpty(area)) {
return {};
}
const distance = await service.course.v3.lbs.getDistance({ lng: location.lng, lat: location.lat }, { lng: area.lng, lat: area.lat });
// 暂定3公里以内步行
area.travel_method = distance < 3000 ? 'walking' : 'driving';
const lbsResult = await service.course.v3.lbs.getLBSDistance(area.travel_method, { lng: location.lng, lat: location.lat }, [{ lng: area.lng, lat: area.lat }]);
if (lbsResult.results.length > 0) {
area.distance = parseFloat((lbsResult.results[0].distance / 1000).toFixed(1));
area.duration = lbsResult.results[0].duration;
const minute = area.travel_method === 'walking' ? Math.ceil(area.distance / 0.08) : Math.ceil(area.duration / 60);
area.travel_tips = area.travel_method === 'walking' ? `距我${area.distance}km,步行${minute}分钟` : `距我${area.distance}km,开车${minute}分钟`;
} else {
area.distance = 999999;
area.duration = 0;
area.travel_tips = '';
}
return area;
}
// 机构列表
async getInstitutions(input) {
const { ctx } = this;
const userUuid = ctx.userUuid;
const { cat_id, age, address } = input;
const lat = ctx.isEmpty(input.lat) ? 0 : input.lat;
const lng = ctx.isEmpty(input.lng) ? 0 : input.lng;
// 保存定位记录
if (address && lat && lng) {
ctx.classModel.V3.CourseLogUserGps.create({ user_uuid: userUuid, address, lat, lng, created_time: moment().format('YYYY-MM-DD HH:mm:ss') });
}
let institutionList = [];
let institutionCats = [];
const filter = { where: { status: 'online', is_deleted: 0 } };
// 年龄筛选
if (Number(age) > 0) {
filter.where.min_age = { $lte: age };
filter.where.max_age = { $gte: age };
}
// 分类筛选
if (Number(cat_id) > 0) {
// 如果是一级分类,则需要加入该分类下所有子分类
const cats = await ctx.classModel.V3.CourseCat.findAll({ where: { parent_id: cat_id, status: 'online', is_deleted: 0 } });
const catIds = R.pluck('id', cats);
catIds.push(Number(cat_id));
institutionCats = await ctx.classModel.V3.CourseInstitutionToCat.findAll({ where: { status: 'online', is_deleted: 0, cat_id: { $in: catIds } } });
const institutionIds = R.pluck('institution_id', institutionCats);
filter.where.id = { $in: institutionIds };
}
institutionList = await ctx.classModel.V3.CourseInstitution.findAll(filter);
institutionList = R.pluck('dataValues', institutionList);
const ret = await this.formatInstitutionList(institutionList, input);
return ret;
}
// 格式化机构列表
async formatInstitutionList(institutionList, input) {
const { ctx, service } = this;
const userUuid = ctx.userUuid;
const distance = Number(input.distance) || 0;
const page = Number(input.page) || 1;
const limit = Number(input.limit) || 10;
const { lng, lat } = input;
let institutionCats = [];
let institutionIds = R.pluck('id', institutionList);
// 获取机构对应的校区
const areaList = await ctx.classModel.V3.CourseArea.findAll({ where: { status: 'online', is_deleted: 0, institution_id: { $in: institutionIds } } });
// 计算校区对应的距离
const areaHandle = [];
for (const v of areaList) {
areaHandle.push(service.course.v3.lbs.getDistance({ lat, lng }, { lat: v.lat, lng: v.lng }));
}
const areaDistanceCalcResult = await Promise.all(areaHandle).then(result => {
return result;
}).catch(error => {
ctx.failed(error);
});
for (const i in areaList) {
areaList[i].distance = areaDistanceCalcResult[i];
}
// 选出最短距离的校区
for (const v of areaList) {
for (const i in institutionList) {
if (institutionList[i].id === v.institution_id) {
if (ctx.isEmpty(institutionList[i].area) || (institutionList[i].area.distance > v.distance)) {
institutionList[i].area = v;
}
}
}
}
// 格式化机构距离
for (const i in institutionList) {
institutionList[i].distance = ctx.isEmpty(institutionList[i].area) ? 999999.0 : institutionList[i].area.distance;
}
institutionList = _.orderBy(institutionList, [ 'distance' ], [ 'asc' ]);
// 距离筛选
if (Number(distance) > 0) {
for (const i in institutionList) {
if (institutionList[i].distance > Number(distance)) {
institutionList = institutionList.slice(0, i);
break;
}
}
}
// 分页
const ret = {
results: [],
count: institutionList.length,
};
institutionList = institutionList.slice(Number(page - 1) * Number(limit), Number(page) * Number(limit));
institutionIds = R.pluck('id', institutionList);
// 获取所有分类
const allCats = await ctx.classModel.V3.CourseCat.findAll({ where: { status: 'online', is_deleted: 0 } });
const catList = [];
for (const v of allCats) {
catList[v.id] = v;
}
institutionCats = await ctx.classModel.V3.CourseInstitutionToCat.findAll({ where: { status: 'online', is_deleted: 0, institution_id: { $in: institutionIds } } });
// 获取所有标签
const allTags = await ctx.classModel.V3.CourseTag.findAll({ where: { status: 'online', is_deleted: 0 } });
const tagList = [];
for (const v of allTags) {
tagList[v.id] = v;
}
// 用户已收藏机构列表
const userInstitutions = await ctx.classModel.V3.CourseUserCollection.findAll({ where: { is_deleted: 0, user_uuid: userUuid } });
const userInstitutionIds = R.pluck('institution_id', userInstitutions);
// 机构图片及格式化
for (const i in institutionList) {
// 格式化机构分类(展示一级分类)
let cats = [];
for (const v of institutionCats) {
if (v.institution_id === institutionList[i].id && catList[v.cat_id]) {
if (catList[v.cat_id].parent_id === 0) {
cats.push(v.cat_id);
} else {
cats.push(catList[v.cat_id].parent_id);
}
}
}
// 去重
cats = _.uniq(cats);
institutionList[i].cats = [];
for (const v of cats) {
institutionList[i].cats.push(catList[v]);
}
// 标签
let tags = await ctx.classModel.V3.CourseInstitutionToTag.findAll({ where: { institution_id: institutionList[i].id, status: 'online', is_deleted: 0 } });
tags = _.orderBy(tags, [ 'sort' ], [ 'asc' ]);
institutionList[i].tags = [];
for (const v of tags) {
institutionList[i].tags.push(tagList[v.tag_id]);
}
// 优先获取机构详情图
let institutionImages = await ctx.classModel.V3.CourseImages.findAll({ where: { type: 1, type_id: institutionList[i].id, status: 'online', is_deleted: 0 }, order: [[ 'sort', 'asc' ]], limit: 3 });
if (institutionImages.length < 3) {
const defaultImages = await ctx.classModel.V3.CourseImages.findAll({ where: { type: 4, type_id: { $in: cats }, status: 'online', is_deleted: 0 }, limit: 3 - institutionImages.length });
institutionImages = institutionImages.concat(defaultImages);
}
institutionList[i].images = institutionImages;
institutionList[i].distance = String((institutionList[i].distance / 1000).toFixed(1)) + 'km';
institutionList[i].phone = institutionList[i].area.length > 0 ? institutionList[i].area[0].phone : '';
// 是否已收藏
institutionList[i].is_collected = userInstitutionIds.includes(institutionList[i].id) ? 1 : 0;
ret.results.push(institutionList[i]);
}
return ret;
}
// 搜索
async search(input) {
const { ctx } = this;
const { cat_id, age, search } = input;
const userUuid = ctx.userUuid;
// 保存搜索记录
if (search) {
const searchInfo = await ctx.classModel.V3.CourseSearch.findOne({ where: { content: search, is_deleted: 0 } });
if (searchInfo) {
await ctx.classModel.V3.CourseSearch.update({ count: searchInfo.count + 1 }, { where: { id: searchInfo.id } });
} else {
await ctx.classModel.V3.CourseSearch.create({ content: search, sort: 0, count: 1, is_hot: 0, status: 'online', is_deleted: 0, created_time: moment().format('YYYY-MM-DD HH:mm:ss'), updated_time: moment().format('YYYY-MM-DD HH:mm:ss') });
}
await ctx.classModel.V3.CourseUserSearch.create({ user_uuid: userUuid, content: search, status: 'online', created_time: moment().format('YYYY-MM-DD HH:mm:ss'), updated_time: moment().format('YYYY-MM-DD HH:mm:ss') });
}
let institutionList = [];
let institutionCats = [];
let institutionIds = [];
const filter = { where: { status: 'online', is_deleted: 0 }, row: true };
const classFilter = { where: { status: 'online', is_deleted: 0, name: { $like: '%' + search + '%' } }, attributes: [ 'id', 'institution_id', 'name', 'status', 'is_deleted' ], row: true };
// 年龄筛选
if (Number(age) > 0) {
filter.where.min_age = { $lte: age };
filter.where.max_age = { $gte: age };
}
// 分类筛选
if (Number(cat_id) > 0) {
// 如果是一级分类,则需要加入该分类下所有子分类
const cats = await ctx.classModel.V3.CourseCat.findAll({ where: { parent_id: cat_id, status: 'online', is_deleted: 0 } });
const catIds = R.pluck('id', cats);
catIds.push(Number(cat_id));
institutionCats = await ctx.classModel.V3.CourseInstitutionToCat.findAll({ where: { status: 'online', is_deleted: 0, cat_id: { $in: catIds } } });
institutionIds = R.pluck('institution_id', institutionCats);
}
if (institutionIds.length > 0) {
filter.where.id = { $in: institutionIds };
classFilter.where.institution_id = { $in: institutionIds };
}
const institutionFilter = filter;
institutionFilter.where.name = { $like: '%' + search + '%' };
institutionList = await ctx.classModel.V3.CourseInstitution.findAll(institutionFilter);
const ids = R.pluck('id', institutionList);
// 课程搜索
const classList = await ctx.classModel.V3.CourseClass.findAll(classFilter);
// 去除已经搜到的机构
const classInstitutionIds = _.difference(R.pluck('institution_id', classList), ids);
if (classInstitutionIds.length > 0) {
filter.where.id = { $in: classInstitutionIds };
const classInstitutionList = await ctx.classModel.V3.CourseInstitution.findAll(filter);
institutionList = institutionList.concat(classInstitutionList);
}
institutionList = R.pluck('dataValues', institutionList);
const ret = await this.formatInstitutionList(institutionList, input);
return ret;
}
// 获取热搜
async getHotSearch() {
const { ctx } = this;
const search = await ctx.classModel.V3.CourseSearch.findAll({ where: { status: 'online', is_deleted: 0, is_hot: 1 }, order: [[ 'sort', 'asc' ]] });
const ret = {
results: R.pluck('content', search),
count: search.length,
};
return ret;
}
// 获取用户搜索历史
async getUserSearch() {
const { ctx } = this;
const userUuid = ctx.userUuid;
const search = await ctx.classModel.V3.CourseUserSearch.findAll({ where: { status: 'online', user_uuid: userUuid }, limit: 10, order: [[ 'id', 'desc' ]] });
const result = _.uniq(R.pluck('content', search));
const ret = {
results: result,
count: result.length,
};
return ret;
}
// 删除用户搜索历史
async deleteUserSearch() {
const { ctx } = this;
const userUuid = ctx.userUuid;
await ctx.classModel.V3.CourseUserSearch.update({ status: 'offline' }, { where: { user_uuid: userUuid, status: 'online' } });
return;
}
// 评论列表
async getComments(input) {
const { ctx } = this;
const page = Number(input.page) || 1;
const limit = Number(input.limit) || 10;
const offset = (page - 1) * limit;
const institution_id = Number(input.institution_id) || 0;
const comments = await ctx.classModel.V3.CourseComment.findAndCountAll({ where: { institution_id, status: 'online', is_deleted: 0 }, offset, limit, order: [[ 'id', 'desc' ]] });
comments.rows = R.pluck('dataValues', comments.rows);
const ret = {
results: await this.formatComments(comments.rows),
count: comments.count,
};
return ret;
}
// 格式化评论列表
async formatComments(comments) {
const { ctx } = this;
if (ctx.isEmpty(comments)) {
return [];
}
const ids = R.pluck('id', comments);
const images = await ctx.classModel.V3.CourseImages.findAll({ where: { type: 3, type_id: { $in: ids }, status: 'online', is_deleted: 0 } });
const ret = [];
for (const v of comments) {
// 评论图片
let commentImages = [];
for (const j of images) {
if (j.type_id === v.id) {
commentImages.push(j);
}
}
commentImages = _.orderBy(commentImages, [ 'sort' ], [ 'asc' ]);
ret.push({
id: v.id,
institution_id: v.institution_id,
user_uuid: v.user_uuid,
nickname: v.nickname,
avatar: v.avatar,
content: v.content,
has_image: v.has_image,
images: commentImages,
created_time: v.created_time,
});
}
return ret;
}
// 搜索关联
async getSuggestSearch(input) {
const { ctx } = this;
const { search } = input;
let results = [];
if (!ctx.isEmpty(search)) {
const institutions = await ctx.classModel.V3.CourseInstitution.findAll({ where: { status: 'online', is_deleted: 0, name: { $like: '%' + search + '%' } }, limit: 10, attributes: [ 'id', 'name', 'status', 'is_deleted' ] });
results = R.pluck('name', institutions);
}
const ret = {
results,
count: results.length,
};
return ret;
}
// 教师详情
async getTeacher(id) {
const { ctx } = this;
let teacher = await ctx.classModel.V3.CourseTeacher.findOne({ where: { id, status: 'online', is_deleted: 0 } });
if (ctx.isEmpty(teacher)) {
ctx.failed('数据不存在');
}
teacher = teacher.dataValues;
teacher.point_tags = teacher.point ? teacher.point.split(',') : [];
teacher.work_experience_tags = teacher.work_experience ? teacher.work_experience.split(';') : [];
const institution = await ctx.classModel.V3.CourseInstitution.findOne({ where: { id: teacher.institution_id } });
teacher.institution_name = institution.name;
return teacher;
}
// 教师列表
async getTeachers(input) {
const { ctx } = this;
const page = Number(input.page) || 1;
const limit = Number(input.limit) || 10;
const offset = (page - 1) * limit;
const institutionId = Number(input.institution_id) || 0;
const teachers = await ctx.classModel.V3.CourseTeacher.findAndCountAll({ where: { institution_id: institutionId, status: 'online', is_deleted: 0 }, raw: true, offset, limit });
const ret = {
results: teachers.rows,
count: teachers.count,
};
return ret;
}
// 课程列表
async getClasses(input) {
const { ctx } = this;
const page = Number(input.page) || 1;
const limit = Number(input.limit) || 10;
const offset = (page - 1) * limit;
const institutionId = Number(input.institution_id) || 0;
const attributes = [ 'id', 'institution_id', 'name', 'type', 'price', 'status', 'is_deleted' ];
const classes = await ctx.classModel.V3.CourseClass.findAndCountAll({ where: { institution_id: institutionId, status: 'online', is_deleted: 0 }, raw: true, offset, limit, attributes });
const classIds = R.pluck('id', classes.rows);
// 课程图片
const classImages = await ctx.classModel.V3.CourseImages.findAll({ where: { type: 2, type_id: { $in: classIds }, status: 'online', is_deleted: 0 }, raw: true });
for (const i in classes.rows) {
let images = [];
for (const v of classImages) {
if (v.type_id === classes.rows[i].id) {
images.push(v);
}
}
images = _.orderBy(images, [ 'sort' ], [ 'asc' ]);
classes.rows[i].image = images.length > 0 ? images[0] : {};
}
const ret = {
results: classes.rows,
count: classes.count,
};
return ret;
}
// 课程详情
async getClass(id) {
const { ctx } = this;
const classInfo = await ctx.classModel.V3.CourseClass.findOne({ where: { id, status: 'online', is_deleted: 0 }, raw: true });
if (ctx.isEmpty(classInfo)) {
ctx.failed('数据不存在');
}
// 课程图片
let images = await ctx.classModel.V3.CourseImages.findAll({ where: { type: 2, type_id: id, status: 'online', is_deleted: 0 }, raw: true });
// 去重
images = _.uniqBy(images, function(v) { return (v.is_image === 1 ? v.image_url : v.video_url); });
images = _.orderBy(images, [ 'sort' ], [ 'asc' ]);
classInfo.images = images;
return classInfo;
}
// 用户收藏机构列表
async getUserCollectedInstitutions(input) {
const { ctx } = this;
const userUuid = ctx.userUuid;
const userCollection = await ctx.classModel.V3.CourseUserCollection.findAll({ where: { user_uuid: userUuid, is_deleted: 0 }, raw: true });
const institutionIds = R.pluck('institution_id', userCollection);
const institutionList = await ctx.classModel.V3.CourseInstitution.findAll({ where: { id: { $in: institutionIds }, status: 'online', is_deleted: 0 }, raw: true });
const ret = await this.formatInstitutionList(institutionList, input);
return ret;
}
}
module.exports = InstitutionSubService;
'use strict';
const Service = require('egg').Service;
class LbsService extends Service {
// 计算两个经纬度之间的距离,用Haversine公式,单位:m
async getDistance(from, to) {
const rad_from = from.lat * Math.PI / 180.0;
const rad_to = to.lat * Math.PI / 180.0;
const sub_lat = rad_from - rad_to;
const sub_lng = from.lng * Math.PI / 180.0 - to.lng * Math.PI / 180.0;
const r = 6378137; // 地球半径
const distance = r * 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(sub_lat / 2), 2) + Math.cos(rad_from) * Math.cos(rad_to) * Math.pow(Math.sin(sub_lng / 2), 2)));
return distance;
}
// 腾讯位置服务WebService API,距离计算(一对多)
// 返回示例:
// {
// "results": [
// {
// "from": {
// "lat": 39.983171,
// "lng": 116.308479
// },
// "to": {
// "lat": 39.996059,
// "lng": 116.353454
// },
// "distance": 5354.7,
// "duration": 1678
// },
// {
// "from": {
// "lat": 39.983171,
// "lng": 116.308479
// },
// "to": {
// "lat": 39.949226,
// "lng": 116.394309
// },
// "distance": 10495.8, // 起点到终点的距离,单位:米。如果radius半径过小或者无法搜索到,则返回-1
// "duration": 2635 // 表示从起点到终点的结合路况的时间,秒为单位。 注:步行方式不计算耗时,该值始终为0
// }
// ]
// }
async getLBSDistance(mode = 'driving', from, toArr) {
const { ctx } = this;
const to = [];
let result = [];
for (const v of toArr) {
to.push(String(v.lat) + ',' + String(v.lng));
}
console.info(from);
const params = {
mode, // driving, walking;默认driving
from: String(from.lat) + ',' + String(from.lng),
to: to.join(';'),
key: ctx.app.config.TX_LBS_KEY,
};
const resp = await ctx.helper.send_request(ctx.app.config.TX_LBS_DISTANCE_URL, params, { method: 'GET' });
// resp.data.status 状态码,0:正常,310:请求参数信息有误,311:Key格式错误,306:请求有护持信息请检查字符串,110:请求来源未被授权
ctx.logger.info('tx_lbs_distance_params: ' + JSON.stringify(params));
ctx.logger.info('tx_lbs_distance_resp: ' + JSON.stringify(resp));
if (resp.status === 200) {
// 判断响应是否正确
if (resp.data.status === 0) {
result = resp.data.result.elements;
}
}
return { results: result };
}
// 地址解析
async getLBSAddress(address) {
const { ctx } = this;
let result = {};
const params = {
address,
key: ctx.app.config.TX_LBS_KEY,
};
const resp = await ctx.helper.send_request(ctx.app.config.TX_LBS_ADDRESS_URL, params, { method: 'GET' });
// resp.data.status 状态码,0:正常,310:请求参数信息有误,311:Key格式错误,306:请求有护持信息请检查字符串,110:请求来源未被授权
ctx.logger.info('tx_lbs_address_resp: ' + JSON.stringify(resp));
if (resp.status === 200) {
// 判断响应是否正确
if (resp.data.status === 0) {
result = resp.data.result;
}
}
return { result };
}
// 逆地址解析
async getLBSLocation(location) {
const { ctx } = this;
let result = {};
const params = {
location: location.lat + ',' + location.lng,
key: ctx.app.config.TX_LBS_KEY,
};
const resp = await ctx.helper.send_request(ctx.app.config.TX_LBS_ADDRESS_URL, params, { method: 'GET' });
// resp.data.status 状态码,0:正常,310:请求参数信息有误,311:Key格式错误,306:请求有护持信息请检查字符串,110:请求来源未被授权
ctx.logger.info('tx_lbs_location_resp: ' + JSON.stringify(resp));
if (resp.status === 200) {
// 判断响应是否正确
if (resp.data.status === 0) {
result = resp.data.result;
}
}
return { result };
}
// 逆地址解析
async getLBSIp() {
const { ctx } = this;
let result = {};
const ip = ctx.helper.getClientIP();
if (!ip) {
return result;
}
const params = {
ip,
key: ctx.app.config.TX_LBS_KEY,
};
const resp = await ctx.helper.send_request(ctx.app.config.TX_LBS_URL + '/location/v1/ip ', params, { method: 'GET' });
// resp.data.status 状态码,0:正常,310:请求参数信息有误,311:Key格式错误,306:请求有护持信息请检查字符串,110:请求来源未被授权
ctx.logger.info('tx_lbs_ip_resp: ' + JSON.stringify(resp));
if (resp.status === 200) {
// 判断响应是否正确
if (resp.data.status === 0) {
result = resp.data.result;
}
}
return { result };
}
}
module.exports = LbsService;
'use strict';
const Service = require('egg').Service;
const AGE_CATS = [
{ id: -2, name: '全部', value: 0 },
{ id: -3, name: '3岁以下', value: 3 },
{ id: -4, name: '4岁', value: 4 },
{ id: -5, name: '5岁', value: 5 },
{ id: -6, name: '6岁', value: 6 },
{ id: -7, name: '7岁', value: 7 },
{ id: -8, name: '8岁', value: 8 },
{ id: -9, name: '9岁', value: 9 },
{ id: -10, name: '10岁', value: 10 },
{ id: -11, name: '11岁', value: 11 },
{ id: -12, name: '12岁', value: 12 },
{ id: -13, name: '12岁以上', value: 13 },
];
const INSTITUTION_TYPE = [
{ id: -14, name: '全部', value: '' },
{ id: -15, name: '品牌', value: '品牌' },
];
const DISTANCES = [
{ id: -16, name: '全部', value: 0 },
{ id: -17, name: '500米以内', value: 500 },
{ id: -18, name: '1公里以内', value: 1000 },
{ id: -19, name: '2公里以内', value: 2000 },
{ id: -20, name: '3公里以内', value: 3000 },
{ id: -21, name: '5公里以内', value: 5000 },
];
class OptionService extends Service {
async getOptions() {
const { service } = this;
const cats = await service.course.v3.institution.getCats();
const options = {
cats,
ages: AGE_CATS,
institutions: INSTITUTION_TYPE,
distances: DISTANCES,
};
return options;
}
async getBanners(alias) {
const { ctx } = this;
const bannerType = await ctx.classModel.V3.CourseBannerType.findOne({ where: { alias, status: 'online', is_deleted: 0 }, row: true });
if (ctx.isEmpty(bannerType)) {
ctx.failed('数据不存在');
}
const banners = await ctx.classModel.V3.CourseBanner.findAll({ where: { type_id: bannerType.id, status: 'online', is_deleted: 0 }, order: [[ 'sort', 'asc' ]], row: true });
const ret = {
results: banners,
count: banners.length,
};
return ret;
}
}
module.exports = OptionService;
'use strict';
const Service = require('egg').Service;
const moment = require('moment');
const R = require('ramda');
const GENDER = {
boy: '小王子',
girl: '小公主',
};
class UserService extends Service {
async getBabyInfo() {
const { ctx } = this;
const user_uuid = ctx.userUuid;
const where = { user_uuid, is_deleted: 0 };
const babys_info = await ctx.classModel.V3.CourseUserBaby.all({ where, order: [[ 'id', 'desc' ]] });
const babys = [];
for (const i in babys_info) {
const baby_info = babys_info[i];
babys.push({
id: baby_info.id,
gender: baby_info.gender,
birth: baby_info.birth,
address: baby_info.address,
lat: baby_info.lat,
lng: baby_info.lng,
gender_text: GENDER[baby_info.gender],
age: moment().format('YYYY') - baby_info.birth.substr(0, 4),
});
}
if (babys.length === 0) {
return {};
}
const user_info = { address: babys[0].address, lat: babys[0].lat, lng: babys[0].lng };
return { user_info, babys };
}
async saveBabyInfo(input) {
const { ctx } = this;
const user_uuid = ctx.userUuid;
const { id, gender, birth, address, lat, lng } = input;
const where = { id, user_uuid, is_deleted: 0 };
if (id) {
await ctx.classModel.V3.CourseUserBaby.edit({ params: { gender, birth, address, lat, lng }, where });
} else {
await ctx.classModel.V3.CourseUserBaby.add({ user_uuid, gender, birth, address, lat, lng });
}
return true;
}
async delBabyInfo() {
const { ctx } = this;
const user_uuid = ctx.userUuid;
await ctx.classModel.V3.CourseUserBaby.edit({ params: { is_deleted: 1 }, where: { user_uuid } });
return true;
}
async collectInstitution(institution_id) {
const { ctx } = this;
const user_uuid = ctx.userUuid;
const where = { user_uuid, is_deleted: 0, institution_id };
let ret = await ctx.classModel.V3.CourseUserCollection.one({ where });
if (ret && ret.id) {
return ret.id;
}
ret = await await ctx.classModel.V3.CourseUserCollection.add({ user_uuid, institution_id });
return ret;
}
async delCollectInstitution(institution_id) {
const { ctx } = this;
const user_uuid = ctx.userUuid;
const where = { user_uuid, is_deleted: 0, institution_id };
let ret = await ctx.classModel.V3.CourseUserCollection.one({ where });
if (!ret || !ret.id) {
return true;
}
ret = await await ctx.classModel.V3.CourseUserCollection.edit({ params: { is_deleted: 1 }, where });
return true;
}
async requestWxAuth(code) {
const { ctx } = this;
const APPID = 'wx07a5f0ed5bdf4751';
const SECRET = 'a1b2d32b018988176181497bd74a0b7d';
const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${APPID}&secret=${SECRET}&js_code=${code}&grant_type=authorization_code`;
const result = await ctx.helper.send_request(url, {}, { method: 'GET' });
// const result = {"data":{"session_key":"Ce7HE1+MXfyZpWLYmkP0Iw==","openid":"oSjKI5LlG6AF7_vdV5Qb_DsbHcf4"},"status":200,"headers":{"connection":"keep-alive","content-type":"text/plain","date":"Tue, 24 Sep 2019 06:18:58 GMT","content-length":"82"},"res":{"status":200,"statusCode":200,"statusMessage":"OK","headers":{"connection":"keep-alive","content-type":"text/plain","date":"Tue, 24 Sep 2019 06:18:58 GMT","content-length":"82"},"size":82,"aborted":false,"rt":113,"keepAliveSocket":false,"data":{"session_key":"Ce7HE1+MXfyZpWLYmkP0Iw==","openid":"oSjKI5LlG6AF7_vdV5Qb_DsbHcf4"},"requestUrls":["https://api.weixin.qq.com/sns/jscode2session?appid=wx4769ebba9b91f8ec&secret=680440637b4e38c9b66529cfd5dc590e&js_code=021678ss18NNAk0Fohps1oA6ss1678sT&grant_type=authorization_code"],"timing":{"queuing":15,"dnslookup":15,"connected":27,"requestSent":57,"waiting":111,"contentDownload":113},"remoteAddress":"101.227.162.120","remotePort":443,"socketHandledRequests":1,"socketHandledResponses":1}};
ctx.logger.info(JSON.stringify({ course_mini_auth_ret: result }));
if (result.status !== 200) {
ctx.failed('授权失败');
}
const ret = result.data;
if (!ret.session_key && !ret.openid && ret.errcode !== 0) {
ctx.failed(ret.errmsg);
}
const openid = ret.openid;
const session_key = ret.session_key;
return { openid, session_key };
}
// 获取用户信息
async getUserInfo() {
const { ctx } = this;
const userUuid = ctx.userUuid;
const userInfo = await ctx.classModel.V3.CourseUser.findOne({ where: { uuid: userUuid, is_deleted: 0 } });
if (ctx.isEmpty(userInfo)) {
ctx.failed('用户不存在');
}
let need_bind_phone = 0;
if (ctx.isEmpty(userInfo.phone)) {
const userLog = await ctx.classModel.V3.CourseLogUser.findOne({ where: { user_uuid: userUuid, type: 1 }, order: [[ 'id', 'desc' ]], limit: 1 });
if ((!ctx.isEmpty(userLog) && moment(userLog.created_time, 'YYYY-MM-DD HH:mm:ss').format('YYYY-MM-DD') !== moment().format('YYYY-MM-DD')) || ctx.isEmpty(userLog)) {
need_bind_phone = 1;
await ctx.classModel.V3.CourseLogUser.create({ user_uuid: userUuid, type: 1, created_time: moment().format('YYYY-MM-DD HH:mm:ss') });
}
}
const ret = {
bind_phone: ctx.isEmpty(userInfo.phone) ? 0 : 1,
need_bind_phone,
};
return ret;
}
// 保存用户信息
async registerUserInfo(input) {
const { ctx } = this;
const userUuid = ctx.userUuid;
const { avatar, nickname, province, country, sex, city, encryptedData, iv } = input;
// 查找用户是否存在并更新
const user = await ctx.classModel.V3.CourseUser.findOne({ where: { uuid: userUuid, is_deleted: 0 } });
if (ctx.isEmpty(user)) {
ctx.failed('用户不存在');
}
const data = {};
let bind_phone = ctx.isEmpty(user.phone) ? 0 : 1;
if (!ctx.isEmpty(avatar)) {
data.avatar = avatar;
}
if (!ctx.isEmpty(nickname)) {
data.nickname = nickname;
}
if (!ctx.isEmpty(encryptedData) && !ctx.isEmpty(iv)) {
const decoded = await ctx.service.course.v3.wechat.decodeData(encryptedData, iv);
if (!ctx.isEmpty(decoded) && !ctx.isEmpty(decoded.phoneNumber)) {
data.phone = decoded.phoneNumber;
bind_phone = 1;
}
}
await ctx.classModel.V3.CourseUser.update(data, { where: { id: user.id } });
const ret = {
result: {
bind_phone,
},
};
return ret;
}
}
module.exports = UserService;
'use strict';
const Service = require('egg').Service;
const APPID = 'wx07a5f0ed5bdf4751';
const SECRET = 'a1b2d32b018988176181497bd74a0b7d';
const fs = require('fs');
const request = require('request');
const crypto = require('crypto');
class WechatService extends Service {
async getAccessToken() {
const { ctx } = this;
const url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' + APPID + '&secret=' + SECRET;
// 先从redis中取
let token = await this.app.memcache.get('wechat_course_v3_accessToken');
if (ctx.isEmpty(token)) {
const resp = await ctx.helper.send_request(url, {}, { method: 'GET' });
if (resp.status === 200 && !ctx.isEmpty(resp.data) && !ctx.isEmpty(resp.data.access_token)) {
token = resp.data.access_token;
await this.app.memcache.set('wechat_course_v3_accessToken', token, resp.data.expires_in);
}
}
return token;
}
async callbackAction() {
const { ctx, app } = this;
ctx.logger.info('course_wechat_v3_receive: ' + JSON.stringify(ctx.request.body));
await this.sendMsg(ctx.request.body);
}
async sendMsg(data) {
const { ctx, app } = this;
if (ctx.isEmpty(data)) {
return;
}
// 只有MsgType为miniprogrampage才发送
if (ctx.isEmpty(data.MsgType) || data.MsgType !== 'miniprogrampage') {
return;
}
let mediaId = await this.app.memcache.get('course_wechat_v3_service_image_media_id');
if (ctx.isEmpty(mediaId)) {
const images = 'data:image/png;base64,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';
const image = images.replace(/^data:image\/\w+;base64,/, '');
const imgBuffer = Buffer.from(image, 'base64');
fs.writeFile('image.jpg', imgBuffer, function(err) {});
const file = fs.createReadStream('image.jpg');
const imageInfo = { media: file };
mediaId = await this.uploadTempMedia(imageInfo);
await this.app.memcache.set('course_wechat_v3_service_image_media_id', mediaId, 72 * 3600);
}
if (ctx.isEmpty(mediaId)) {
return;
}
const token = await this.getAccessToken();
const url = 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=' + token;
const params = {
access_token: token,
touser: data.FromUserName,
msgtype: 'image',
image: { media_id: mediaId },
};
const resp = await ctx.helper.send_request(url, params, { method: 'POST' });
ctx.logger.info('course_wechat_v3_resp: ' + JSON.stringify(resp));
return;
}
async getTempMedia(mediaId) {
const { ctx } = this;
const url = 'https://api.weixin.qq.com/cgi-bin/media/get';
const image = await this.app.memcache.get('course_wechat_v3_service_image');
if (image) {
return image;
}
const token = await this.getAccessToken();
const params = {
access_token: token,
media_id: mediaId,
};
const resp = await ctx.helper.send_request(url, params, { method: 'GET' });
let ret = '';
if (resp.status === 200 && !ctx.isEmpty(resp.data) && resp.data.errCode === 0) {
await this.app.memcache.set('course_wechat_v3_service_image', resp.data.buffer, 72 * 3600);
ret = resp.data.buffer;
}
return ret;
}
/*
把媒体文件上传到微信服务器。目前仅支持图片。用于发送客服消息或被动回复用户消息。
type: 'image',
media: {
contentType: 'image/png',
value: Buffer
}
*/
async uploadTempMedia(data) {
const { ctx, app } = this;
if (ctx.isEmpty(data)) {
return;
}
const token = await this.getAccessToken();
const url = 'https://api.weixin.qq.com/cgi-bin/media/upload?access_token=' + token + '&type=image';
// if (resp.status === 200 && !ctx.isEmpty(resp.data) && resp.data.errCode === 0) {
// await this.app.memcache.set('course_wechat_v3_service_image_media_id', resp.data.mediaId, 72*3600);
// ret = resp.data.mediaId;
// }
const ret = await new Promise((resolve, reject) => {
request.post({ url, formData: data }, function(error, response, body) {
const resp = {
statusCode: response.statusCode,
body: response.body,
};
ctx.logger.info('course_wechat_v3_uploadTempMedia_resp: ' + JSON.stringify(resp));
if (response.statusCode === 200) {
const result = JSON.parse(body);
resolve(result.media_id);
} else {
resolve('');
}
});
});
return ret;
}
// 解密
async decodeData(encryptedData, iv) {
const { ctx } = this;
const userUuid = ctx.userUuid;
const userInfo = await ctx.app.memcache.get('course_v3_user_session_' + userUuid);
if (ctx.isEmpty(userInfo) || ctx.isEmpty(userInfo.session_key)) {
ctx.failed('sessionKey不存在');
}
const sessionKey = new Buffer(userInfo.session_key, 'base64');
// base64 decode
encryptedData = new Buffer(encryptedData, 'base64');
iv = new Buffer(iv, 'base64');
try {
// 解密
const decipher = crypto.createDecipheriv('aes-128-cbc', sessionKey, iv);
// 设置自动 padding 为 true,删除填充补位
decipher.setAutoPadding(true);
var decoded = decipher.update(encryptedData, 'binary', 'utf8');
decoded += decipher.final('utf8');
decoded = JSON.parse(decoded);
} catch (err) {
ctx.failed('Illegal Buffer');
}
return decoded;
}
}
module.exports = WechatService;
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