Commit 939d9c3d authored by 成旭东's avatar 成旭东

first commit

parents
'use strict';
module.exports = {
write: true,
plugin: 'autod-egg',
prefix: '^',
devprefix: '^',
exclude: [
'test/fixtures',
'coverage',
],
dep: [
'egg',
'egg-scripts',
],
devdep: [
'autod',
'autod-egg',
'egg-bin',
'tslib',
'typescript',
],
keep: [
],
semver: [
],
test: 'scripts',
};
.git
\ No newline at end of file
**/*.d.ts
node_modules/
{
"parser": "babel-eslint",
"extends": [
"eslint-config-egg"
],
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"experimentalObjectRestSpread": true
}
},
"rules": {
"indent": ["error", 2],
// "array-bracket-spacing": ["error", "never"],
"linebreak-style": "off",
"no-unused-vars": "off"
}
}
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Node.js CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
node-version: [8.x]
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm i -g npminstall && npminstall
- run: npm run ci
env:
CI: true
logs/
npm-debug.log
yarn-error.log
node_modules/
package-lock.json
yarn.lock
coverage/
.idea/
run/
logs/
.DS_Store
.vscode
app/public
*.swp
*.lock
*.js
!.autod.conf.js
app/**/*.js
test/**/*.js
config/**/*.js
app/**/*.map
test/**/*.map
config/**/*.map
variables:
DOCKER_REGISTRY: 't-docker.51gjj.com'
before_script:
- docker login -u pengtianzi -p 4dc8b3a4d91fa7fa $DOCKER_REGISTRY
stages:
- pre_deploy
build_docker_image:
stage: pre_deploy
only:
- dev
- uat
- pro
- tags
script:
#- export IMAGE_TAG=${CI_COMMIT_TAG:-latest}
- docker build -t $DOCKER_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_BUILD_REF_NAME .
- docker push $DOCKER_REGISTRY/$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME:$CI_BUILD_REF_NAME
tags:
- docker-image-builder
\ No newline at end of file
language: node_js
node_js:
- '8'
before_install:
- npm i npminstall -g
install:
- npminstall
script:
- npm run ci
after_script:
- npminstall codecov && codecov
FROM t-docker.51gjj.com/tools/node:8.12.0
#ENV LANG C.UTF-8
ENV NODE_ENV production
ENV PATH $PATH:/usr/local/node/bin/
RUN mkdir -p /usr/src/app
#RUN yum -y install dotnet-sdk-3.1
WORKDIR /usr/src/app
COPY package.json /usr/src/app/
RUN yarn config set registry "https://registry.npm.taobao.org/"
RUN yarn install
COPY . /usr/src/app
EXPOSE 7001
CMD [ "yarn", "docker" ]
# hackernews-async-ts
[Hacker News](https://news.ycombinator.com/) showcase using typescript && egg
## QuickStart
### Development
```bash
$ npm i
$ npm run dev
$ open http://localhost:7001/
```
Don't tsc compile at development mode, if you had run `tsc` then you need to `npm run clean` before `npm run dev`.
### Deploy
```bash
$ npm run tsc
$ npm start
```
### Npm Scripts
- Use `npm run lint` to check code style
- Use `npm test` to run unit test
- se `npm run clean` to clean compiled js at development mode once
### Requirement
- Node.js 8.x
- Typescript 2.8+
import { Controller, Context } from 'egg';
// import moment from 'moment';
export default class EstateAppointmentController extends Controller {
serviceGroupName: string;
serviceName: string;
listRule: any;
updateRule: any;
constructor(ctx: Context) {
super(ctx);
this.serviceGroupName = 'mpEstate';
this.serviceName = 'appointment';
this.listRule = {
project_id: { type: 'number', required: false },
id: { type: 'number', required: false },
user_id: { type: 'string', required: false },
phone: { type: 'string', required: false },
status: { type: 'string', required: false },
operation_at: { type: 'string', required: false },
created_at: { type: 'string', required: false },
};
this.updateRule = {
status: { type: 'string', required: false },
};
}
validateRuleParams(rule: { [x: string]: { required: boolean; }; }, requiredList: any = []) {
requiredList.forEach(item => {
rule[item].required = true;
});
return rule;
}
async list() {
const { ctx, service, listRule, serviceGroupName, serviceName } = this;
const whereInfo = ctx.helper.switchQueryToWhere(ctx.query, {
id: (item: any) => Number(item),
project_id: (item: any) => Number(item),
}, Object.keys(listRule));
const rule = this.validateRuleParams(listRule, []);
ctx.validate(rule, whereInfo);
const where = whereInfo;
const data = await service[serviceGroupName][serviceName].findByPage(ctx.pagination, where, {});
const total = await service[serviceGroupName][serviceName].count(where);
ctx.success({
data,
total,
...ctx.pagination,
});
}
async update() {
const { ctx, service, updateRule, serviceGroupName, serviceName } = this;
const { id } = ctx.params;
const rule = this.validateRuleParams(updateRule, [ 'status' ]);
const updateItem = ctx.helper.switchQueryToWhere(ctx.request.body, {}, Object.keys(updateRule));
ctx.validate(rule, updateItem);
await service[serviceGroupName][serviceName].update(id, {
...updateItem,
operation_at: new Date(),
});
ctx.success();
}
}
This diff is collapsed.
import { Controller, Context } from 'egg';
// import moment from 'moment';
export default class EstateMaterialController extends Controller {
serviceGroupName: string;
serviceName: string;
listRule: any;
updateRule: any;
constructor(ctx: Context) {
super(ctx);
this.serviceGroupName = 'mpEstate';
this.serviceName = 'material';
this.listRule = {
title: { type: 'string', required: false },
alias: { type: 'string', required: false },
};
this.updateRule = {
title: { type: 'string', required: false },
alias: { type: 'string', required: false },
description: { type: 'string', required: false },
status: { type: 'string', required: false },
list: { type: 'object', required: false },
};
}
validateRuleParams(rule: { [x: string]: { required: boolean; }; }, requiredList: any = []) {
requiredList.forEach(item => {
rule[item].required = true;
});
return rule;
}
async list() {
const { ctx, service, listRule, serviceGroupName, serviceName } = this;
const whereInfo = ctx.helper.switchQueryToWhere(ctx.query, {}, Object.keys(listRule));
const rule = this.validateRuleParams(listRule, []);
ctx.validate(rule, whereInfo);
const where = whereInfo;
const data = await service[serviceGroupName][serviceName].findByPage(ctx.pagination, where, {});
const total = await service[serviceGroupName][serviceName].count(where);
ctx.success({
data,
total,
...ctx.pagination,
});
}
async update() {
const { ctx, service, updateRule, serviceGroupName, serviceName } = this;
const { id } = ctx.params;
const rule = this.validateRuleParams(updateRule, []);
const updateItem = ctx.helper.switchQueryToWhere(ctx.request.body, {}, Object.keys(updateRule));
ctx.validate(rule, updateItem);
await service[serviceGroupName][serviceName].update(id, {
...updateItem,
updated_at: new Date(),
});
ctx.success();
}
async create() {
const { ctx, service, updateRule, serviceGroupName, serviceName } = this;
const rule = this.validateRuleParams(updateRule, [ 'title', 'alias', 'description', 'status', 'list' ]);
const newItem = ctx.helper.switchQueryToWhere(ctx.request.body, {}, Object.keys(updateRule));
ctx.validate(rule, newItem);
const item = await service[serviceGroupName][serviceName].create({
...newItem,
created_at: new Date(),
updated_at: new Date(),
});
ctx.success(item);
}
async delete() {
const { ctx, service, serviceGroupName, serviceName } = this;
const { id } = ctx.params;
await service[serviceGroupName][serviceName].delete(id);
ctx.success();
}
}
import { Controller, Context } from 'egg';
// import moment from 'moment';
export default class EstateTagController extends Controller {
serviceGroupName: string;
serviceName: string;
listRule: any;
updateRule: any;
constructor(ctx: Context) {
super(ctx);
this.serviceGroupName = 'mpEstate';
this.serviceName = 'tag';
this.listRule = {
tag: { type: 'string', required: false },
type: { type: 'string', required: false },
status: { type: 'string', required: false },
};
this.updateRule = {
tag: { type: 'string', required: false },
status: { type: 'string', required: false },
is_house: { type: 'number', required: false },
is_business: { type: 'number', required: false },
is_office: { type: 'number', required: false },
};
}
validateRuleParams(rule: { [x: string]: { required: boolean; }; }, requiredList: any = []) {
requiredList.forEach(item => {
rule[item].required = true;
});
return rule;
}
async list() {
const { ctx, service, listRule, serviceGroupName, serviceName } = this;
const whereInfo = ctx.helper.switchQueryToWhere(ctx.query, {}, Object.keys(listRule));
const rule = this.validateRuleParams(listRule, []);
ctx.validate(rule, whereInfo);
const where = whereInfo;
const data = await service[serviceGroupName][serviceName].findByPage(ctx.pagination, where, {});
const total = await service[serviceGroupName][serviceName].count(where);
ctx.success({
data,
total,
...ctx.pagination,
});
}
async all() {
const { ctx, service, serviceGroupName, serviceName } = this;
const data = await service[serviceGroupName][serviceName].findAll();
ctx.success(data);
}
async batchUpdate() {
const { ctx, service, serviceGroupName, serviceName } = this;
const { data, type } = ctx.request.body;
const typeMap = {
house: 'is_house',
business: 'is_business',
office: 'is_office',
};
for (let i = 0; i < data.length; i++) {
const item = data[i];
await service[serviceGroupName][serviceName].update(item.id, {
[typeMap[type]]: item[typeMap[type]],
});
}
ctx.success();
}
async update() {
const { ctx, service, updateRule, serviceGroupName, serviceName } = this;
const { id } = ctx.params;
const rule = this.validateRuleParams(updateRule, []);
const updateItem = ctx.helper.switchQueryToWhere(ctx.request.body, {
is_house: (item: any) => Number(item),
is_business: (item: any) => Number(item),
is_office: (item: any) => Number(item),
}, Object.keys(updateRule));
ctx.validate(rule, updateItem);
await service[serviceGroupName][serviceName].update(id, {
...updateItem,
updated_at: new Date(),
});
ctx.success();
}
async create() {
const { ctx, service, updateRule, serviceGroupName, serviceName } = this;
const rule = this.validateRuleParams(updateRule, [ 'tag', 'status', 'is_house', 'is_business', 'is_office' ]);
const newItem = ctx.helper.switchQueryToWhere(ctx.request.body, {
is_house: (item: any) => Number(item),
is_business: (item: any) => Number(item),
is_office: (item: any) => Number(item),
}, Object.keys(updateRule));
ctx.validate(rule, newItem);
const item = await service[serviceGroupName][serviceName].create({ ...newItem });
ctx.success(item);
}
async delete() {
const { ctx, service, serviceGroupName, serviceName } = this;
const { id } = ctx.params;
await service[serviceGroupName][serviceName].delete(id);
ctx.success();
}
}
import { Controller, Context } from 'egg';
// import moment from 'moment';
export default class EstateVideoController extends Controller {
serviceGroupName: string;
serviceName: string;
listRule: any;
updateRule: any;
constructor(ctx: Context) {
super(ctx);
this.serviceGroupName = 'mpEstate';
this.serviceName = 'video';
this.listRule = {
project_id: { type: 'number', required: false },
project_name: { type: 'string', required: false },
title: { type: 'string', required: false },
};
this.updateRule = {
project_id: { type: 'number', required: false },
title: { type: 'string', required: false },
collection_num: { type: 'number', required: false },
share_num: { type: 'number', required: false },
read_num: { type: 'number', required: false },
is_recommend: { type: 'number', required: false },
is_top: { type: 'number', required: false },
cover_image: { type: 'string', required: false },
url: { type: 'string', required: false },
};
}
validateRuleParams(rule: { [x: string]: { required: boolean; }; }, requiredList: any = []) {
requiredList.forEach(item => {
rule[item].required = true;
});
return rule;
}
async list() {
const { ctx, service, listRule, serviceGroupName, serviceName } = this;
const whereInfo = ctx.helper.switchQueryToWhere(ctx.query, {
project_id: (item: any) => Number(item),
}, Object.keys(listRule));
const rule = this.validateRuleParams(listRule, []);
ctx.validate(rule, whereInfo);
const where = whereInfo;
const data = await service[serviceGroupName][serviceName].findByPage(ctx.pagination, where, {});
const total = await service[serviceGroupName][serviceName].count(where);
ctx.success({
data,
total,
...ctx.pagination,
});
}
async update() {
const { ctx, service, updateRule, serviceGroupName, serviceName } = this;
const { id } = ctx.params;
const rule = this.validateRuleParams(updateRule, []);
const updateItem = ctx.helper.switchQueryToWhere(ctx.request.body, {
project_id: (item: any) => Number(item),
collection_num: (item: any) => Number(item),
share_num: (item: any) => Number(item),
read_num: (item: any) => Number(item),
is_recommend: (item: any) => Number(item),
is_top: (item: any) => Number(item),
}, Object.keys(updateRule));
ctx.validate(rule, updateItem);
await service[serviceGroupName][serviceName].update(id, {
...updateItem,
updated_at: new Date(),
});
ctx.success();
}
async create() {
const { ctx, service, updateRule, serviceGroupName, serviceName } = this;
const rule = this.validateRuleParams(updateRule, [ 'project_id', 'title', 'collection_num', 'share_num', 'read_num', 'is_recommend', 'is_top', 'cover_image', 'url' ]);
const newItem = ctx.helper.switchQueryToWhere(ctx.request.body, {
project_id: (item: any) => Number(item),
collection_num: (item: any) => Number(item),
share_num: (item: any) => Number(item),
read_num: (item: any) => Number(item),
is_recommend: (item: any) => Number(item),
is_top: (item: any) => Number(item),
}, Object.keys(updateRule));
ctx.validate(rule, newItem);
const item = await service[serviceGroupName][serviceName].create({ ...newItem });
ctx.success(item);
}
async delete() {
const { ctx, service, serviceGroupName, serviceName } = this;
const { id } = ctx.params;
await service[serviceGroupName][serviceName].delete(id);
ctx.success();
}
}
import { Controller, Context } from 'egg';
export default class UserLoginController extends Controller {
loginRule: { username: { type: string; required: boolean; }; password: { type: string; required: boolean; }; };
constructor(ctx: Context) {
super(ctx);
this.loginRule = {
username: { type: 'string', required: true },
password: { type: 'string', required: true },
};
}
async login() {
const { loginRule, ctx, app } = this;
// 获取用户端传递过来的参数
const data = ctx.request.body;
ctx.validate(loginRule, data);
// 进行验证数据是否登录成功
if (data.username === 'admin' && data.password === '123456') {
// 生成token
const token = app.jwt.sign(data, app.config.jwt.secret, {
expiresIn: 60 * 60 * 24 * 7,
});
ctx.success(token);
} else {
this.ctx.throw(400, '账号密码错误');
}
}
async session() {
const { ctx } = this;
ctx.success({
id: 1,
name: '后台管理员',
avatar: 'https://r.51gjj.com/webpublic/images/2020428/c4KFphudpkwz.png',
});
}
async menus() {
const { ctx } = this;
ctx.success([{
id: 1,
parent_id: 0,
icon: 'lock',
url: '/mpEstate',
label: '房产小程序',
is_hidden: false,
children: [{
id: 1.1,
parent_id: 1,
icon: 'usergroup-add',
url: '/mpEstate/estateList',
label: '新房房源管理列表',
is_hidden: false,
}, {
id: 1.2,
parent_id: 2,
icon: 'usergroup-add',
url: '/mpEstate/videoList',
label: '视频管理列表',
is_hidden: false,
}, {
id: 1.3,
parent_id: 3,
icon: 'usergroup-add',
url: '/mpEstate/materialConfig',
label: '资源素材管理',
is_hidden: false,
}, {
id: 1.5,
parent_id: 4,
icon: 'usergroup-add',
url: '/mpEstate/estateTagList',
label: '房产标签管理',
is_hidden: false,
}, {
id: 1.6,
parent_id: 5,
icon: 'usergroup-add',
url: '/mpEstate/typeTagList',
label: '业态列表标签管理',
is_hidden: false,
}, {
id: 1.7,
parent_id: 6,
icon: 'usergroup-add',
url: '/mpEstate/reserveList',
label: '预约看房列表',
is_hidden: false,
}],
}]);
}
}
import { Controller } from 'egg';
export default class UtilController extends Controller {
async uploadExcel() {
const { ctx } = this;
const res = await ctx.service.file.saveToOSS({
ext: [ 'xls', 'xlsx' ],
});
const ret = await ctx.service.file.saveToDB(res);
ctx.success(ret);
}
async uploadMedia() {
const { ctx } = this;
const res = await ctx.service.file.saveToCDN({
ext: [ 'jpg', 'jpeg', 'png', 'gift', 'gif', 'svg', 'mp4' ],
});
// const ret = await ctx.service.file.saveToDB(res);
ctx.success(res);
}
}
// const USER = Symbol('Context#User');
module.exports = {
// get user() {
// if (!this[USER]) {
// this[USER] = this.service.usercenter.user.getUserInstance();
// }
// return this[USER];
// },
failed(message: string) {
const method = this.request.method.toLowerCase();
if (method === 'post') {
this.throw(422, message);
} else {
this.throw(400, message);
}
},
success(data: any) {
const method = this.request.method.toLowerCase();
if (method === 'get') {
this.status = 200;
this.body = data || {};
} else if (method === 'post') {
this.status = 201;
this.body = data || {};
} else if (method === 'put' || method === 'delete') {
this.status = data ? 200 : 204;
this.body = data ? data : '';
} else {
this.status = 204;
this.body = '';
}
},
};
import _ from 'lodash';
import crypto from 'crypto';
import querystring from 'querystring';
import uuidv1 from 'uuid/dist/v1';
import moment from 'moment';
module.exports = {
format: {
phone(n: any) {
if (typeof n === 'string' && n.length === 11) {
return n.substr(0, 3) + '****' + n.substr(7);
}
return undefined;
},
},
number: {
fix(n: any) {
if (!_.isNumber(n)) {
return n;
}
const tmp = Number(n.toFixed(4));
if (Math.abs(n - tmp) < 0.000001) {
n = tmp;
}
return n;
},
},
object: {
getValue(obj: any, key: string) {
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;
},
merge(a: any, b: any, keyFn: any) {
if (a instanceof Array && b instanceof Array) {
if (keyFn) {
const ret: Array<any> = [];
const keyList: Array<any> = [];
[ a, b ].forEach(ele => {
for (let i = 0; i < ele.length; i++) {
const item = ele[i];
const key = keyFn(item);
if (keyList.includes(key)) {
continue;
}
ret.push(item);
keyList.push(key);
}
});
return ret;
}
return a.concat(b);
}
return undefined;
},
only(obj: any, keys: any) {
const ret = {};
if (keys instanceof Array) {
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (key in obj) {
ret[key] = obj[key];
}
}
}
return ret;
},
},
parse: {
int(str: any) {
return Math.round(+str) || 0;
},
},
switchQueryToWhere(data = {}, format = {}, whereList: Array<any> = []) {
const tem = {};
Object.keys(data).forEach(item => {
if (whereList.includes(item) && Object.prototype.hasOwnProperty.call(data, item)) {
if (format[item]) {
tem[item] = format[item](data[item]);
} else {
tem[item] = data[item];
}
}
});
return tem;
},
/**
* 处理字段本是number类型,前端却传''或者undefined,导致后续Number转化为0
* @param {object} obj 传入参数
* @param {array} validationParams 验证的参数
*/
filterInvalidParams(obj: any, validationParams: Array<any>) {
Object.keys(obj).forEach(item => {
if (validationParams.includes(item) && (obj[item] === '' || obj[item] === undefined)) {
delete obj[item];
}
});
return obj;
},
switchWhereEqualToOther(data = {}, format = {}) {
Object.keys(format).forEach(item => {
if (Object.prototype.hasOwnProperty.call(data, item) && data[item]) {
data[item] = format[item](data[item]);
}
});
return data;
},
filterWhere(where, whiteList: Array<any> = []) {
Object.keys(where).forEach(item => {
if (!where[item] && where[item] !== 0 && !whiteList.includes(item)) {
delete where[item];
}
if (Array.isArray(where[item]) && where[item].length === 0 && !whiteList.includes(item)) {
delete where[item];
}
});
},
md5(str: any) {
if (!str) return '';
const hash = crypto.createHash('md5');
const ret = hash.update(String(str), 'utf8');
return ret.digest('hex');
},
aes256_cbc_decrypt(crypted: any) {
const iv = '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0';
const key = 'zt2i6cea0fceabb063e3961gg67op345';
crypted = new Buffer(crypted, 'base64').toString('binary');
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
let decoded = decipher.update(crypted, 'binary', 'utf8');
decoded += decipher.final('utf8');
return decoded;
},
checkSum(params: any, time: any) {
const { app } = this;
const { appsecret } = app.config.qiyu;
const nonce = this.md5(JSON.stringify(params)).toLowerCase();
const signString = appsecret + nonce + time;
const shasum = crypto
.createHash('sha1')
.update(new Buffer(signString))
.digest('hex');
return shasum;
},
process(params: any) {
const keys = Object.keys(params)
.filter(key => {
return (
params[key] !== undefined &&
params[key] !== '' &&
[ 'appSecret', 'partner_key', 'sign', 'key' ].indexOf(key) < 0
);
})
.sort();
const result = {};
for (const key of keys) {
result[key] = params[key];
}
return result;
},
buildRequestBody(params: any, needNonceStr = false) {
const { app } = this;
const { appKey, appSecret } = app.config.Chaos_OpenAPI;
params = params || {};
params.appKey = appKey;
params.timestamp = String(Date.now());
if (needNonceStr) {
params.nonce_str = this.genRandomStr(16);
}
const signStr = this.paramSign(appSecret, params);
return { sign: signStr, params };
},
genRandomStr(length: number) {
return crypto.randomBytes(length).toString('hex');
},
paramSign(appSecret: any, params: any) {
if (!params) {
const err = new Error('parameter params missing');
err.name = 'ParameterSignError';
throw err;
}
if (!appSecret) {
const err = new Error('parameter appSecret missing');
err.name = 'ParamSignError';
throw err;
}
const newParams = this.process(params);
let query = querystring.stringify(newParams);
query += '&appSecret=' + appSecret;
const signStr = crypto
.createHash('md5')
.update(query)
.digest('hex')
.toUpperCase();
return signStr;
},
uuid() {
return uuidv1();
},
datetime: {
subtractDay(num: any, dt: any) {
return moment(dt)
.subtract(num, 'd')
.toDate();
},
format(dt: any, format:any) {
return moment(dt).format(format || 'YYYY-MM-DD HH:mm:ss');
},
monthAgo(month: any) {
return moment().subtract(month, 'months');
},
monthBefore(num = 1) {
return [
moment()
.startOf('month')
.subtract(num, 'months')
.format('YYYY-MM-DD'),
moment()
.startOf('month')
.subtract(num, 'days')
.format('YYYY-MM-DD'),
];
},
},
ignoreItem(res = {}, arr: Array<any> = []) {
Object.keys(res).forEach(key => {
if (arr.indexOf(key) >= 0) {
delete res[key];
}
});
return res;
},
decodeUserSid(code: any) {
const userId = code.slice(5);
const rand = 'OU1WjLvZCrRJ7Yo0gE2XDjuuaSAUuaH1bhHPuMymcdfEeKz0igRhXQkMuLTm1';
const begin = userId.slice(0, 1);
let rtn = '';
const codelen = rand.slice(0, 11);
const codenums = rand.slice(11, 23);
let len = codelen.indexOf(begin);
if (len > -1) {
len++;
const arrnums = userId.slice(-len).split('');
for (const v of arrnums) {
rtn += String(codenums.indexOf(v));
}
} else {
return code;
}
return parseInt(rtn) / 2 / 2;
},
};
module.exports = {
get userAgent() {
return this.get('user-agent');
},
};
module.exports = () => {
return async function errorHandler(ctx: any, next: any) {
try {
await next();
} catch (err) {
// 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志
ctx.app.emit('error', err, ctx);
const status = err.status || 500;
// 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
// const error = status === 500 && ctx.app.config.env === 'prod'
// ? 'Internal Server Error'
// : err.message;
const error = err.message;
// 从 error 对象上读出各个属性,设置到响应中
ctx.body = { error };
if (status === 422) {
ctx.body.detail = err.errors;
}
ctx.status = status;
}
};
};
import { Context } from 'egg';
module.exports = (options: any) => {
return async function(ctx: Context, next: any) {
// 拿到传会数据的header 中的token值
const token = ctx.request.header.authorization;
// const method = ctx.method.toLowerCase();
// if (options.exclude && options.exclude.includes(ctx.path)) {
// await next();
// return;
// }
if (options.exclude && options.exclude.some((item: any) => item.split('/')[2] === ctx.path.split('/')[2])) {
await next();
return;
}
// 当前请求时get请求,执行接下来的中间件
if (!token) {
ctx.throw(401, '未登录, 请先登录');
} else {
try {
// 解码token
ctx.app.jwt.verify(token.split(' ')[1], ctx.app.config.jwt.secret);
await next();
} catch (e) {
ctx.throw(401, '登录失效');
}
}
};
};
module.exports = () => {
return async (ctx: any, next: any) => {
if (!ctx.pagination) {
const query = ctx.query;
const config = ctx.app.config;
const pagination: any = {};
Object.defineProperties(pagination, {
defaultSize: {
enumerable: false,
configurable: false,
writable: true,
value: undefined,
},
size: {
enumerable: true,
get() {
let size = Math.min(1000, parseInt(query.size || this.defaultSize || config.default_pagination_size || 10, 10));
if (isNaN(size)) {
size = this.defaultSize || config.default_pagination_size || 10;
}
return size;
},
},
page: {
enumerable: true,
get() {
let page = Math.max(1, parseInt(query.page || 1, 10));
if (isNaN(page)) {
page = 1;
}
return page;
},
},
});
if (query.sorter) {
const sorter = query.sorter;
if (/^(.+)_descend$/.test(sorter)) {
const key = RegExp.$1;
pagination.order = [[ key, 'DESC' ]];
pagination.sort_key = key;
} else if (/^(.+)_ascend$/.test(sorter)) {
const key = RegExp.$1;
pagination.order = [[ key, 'ASC' ]];
pagination.sort_key = key;
}
}
if (query.sort) {
const sorter = query.sort;
if (/^(.+),descend$/.test(sorter)) {
const key = RegExp.$1;
pagination.order = [[ key, 'DESC' ]];
pagination.sort_key = key;
} else if (/^(.+),ascend$/.test(sorter)) {
const key = RegExp.$1;
pagination.order = [[ key, 'ASC' ]];
pagination.sort_key = key;
}
}
ctx.pagination = pagination;
}
await next();
};
};
import { Application } from 'egg';
export default (app: Application) => {
const { controller, router, jwt } = app;
router.post('/api/login', jwt, controller.user.index.login);
router.get('/api/session', jwt, controller.user.index.session);
router.get('/api/session/menus', jwt, controller.user.index.menus);
router.post('/api/upload/media', controller.util.uploadMedia);
// 新房房源管理
router.get('/api/estate/list', controller.mpEstate.list.list);
router.get('/api/estate/:id', controller.mpEstate.list.show);
router.put('/api/estate/update/:id', controller.mpEstate.list.update);
router.get('/api/estate/tag/:type', controller.mpEstate.list.tag);
router.get('/api/estate/list/export', controller.mpEstate.list.export); // 房源数据导出
// 住宅开盘信息更新
router.put('/api/estate/houseOpenInfo/update/:id', controller.mpEstate.list.houseOpenInfoUpdate);
// 住宅开盘信息状态时间
router.post('/api/estate/houseOpenInfo/status', controller.mpEstate.list.houseOpenInfoStatus);
// 办公开盘信息更新
router.put('/api/estate/officeOpenInfo/update/:id', controller.mpEstate.list.officeOpenInfoUpdate);
// 商业开盘信息更新
router.put('/api/estate/businessOpenInfo/update/:id', controller.mpEstate.list.businessOpenInfoUpdate);
// 动态增改
router.put('/api/estate/trend/update/:id', controller.mpEstate.list.trendUpdate);
router.post('/api/estate/trend/add', controller.mpEstate.list.trendCreate);
// 户型图增删改
router.put('/api/estate/houseType/update/:id', controller.mpEstate.list.houseTypeUpdate);
router.post('/api/estate/houseType/add', controller.mpEstate.list.houseTypeCreate);
router.delete('/api/estate/houseType/:id', controller.mpEstate.list.houseTypeDelete);
// 楼盘图片增删
router.post('/api/estate/houseImage/add', controller.mpEstate.list.houseImageCreate);
router.delete('/api/estate/houseImage/:id', controller.mpEstate.list.houseImageDelete);
// 商业办公业态规划位置图增删
router.post('/api/estate/businessOfficeImage/add', controller.mpEstate.list.businessOfficeImageCreate);
router.delete('/api/estate/businessOfficeImage/:id', controller.mpEstate.list.businessOfficeImageDelete);
// 视频管理列表
router.get('/api/estate/video/list', controller.mpEstate.video.list);
router.delete('/api/estate/video/:id', controller.mpEstate.video.delete);
router.put('/api/estate/video/update/:id', controller.mpEstate.video.update);
router.post('/api/estate/video/add', controller.mpEstate.video.create);
// 资源位配置
router.get('/api/estate/material/list', controller.mpEstate.material.list);
router.delete('/api/estate/material/:id', controller.mpEstate.material.delete);
router.put('/api/estate/material/update/:id', controller.mpEstate.material.update);
router.post('/api/estate/material/add', controller.mpEstate.material.create);
// 房产标签管理
router.get('/api/estate/tag/manager/all', controller.mpEstate.tag.all);
router.get('/api/estate/tag/manager/list', controller.mpEstate.tag.list);
router.put('/api/estate/tag/manager/batch/update', controller.mpEstate.tag.batchUpdate);
router.delete('/api/estate/tag/manager/:id', controller.mpEstate.tag.delete);
router.put('/api/estate/tag/manager/update/:id', controller.mpEstate.tag.update);
router.post('/api/estate/tag/manager/add', controller.mpEstate.tag.create);
// 预约看房管理
router.get('/api/estate/appointment/list', controller.mpEstate.appointment.list);
router.put('/api/estate/appointment/update/:id', controller.mpEstate.appointment.update);
};
import { Service } from 'egg';
export default class Test extends Service {
async sayHi(name: string) {
return `hi, ${name}`;
}
}
import { Service } from 'egg';
// node.js 文件操作对象
import fs from 'fs';
// node.js 路径操作对象
import path from 'path';
// 故名思意 异步二进制 写入流
import { write as awaitWriteStream } from 'await-stream-ready';
// 管道读入一个虫洞。
import sendToWormhole from 'stream-wormhole';
import uuid from 'node-uuid';
import _ from 'lodash';
import moment from 'moment';
import tinify from 'tinify';
tinify.key = 'PTpq1yVDHPlgVBZYxyWB91xdjGbt8KnX';
export default class FileService extends Service {
getTmpFilePath(filename: string) {
const ext = path.extname(filename).toLocaleLowerCase();
const uploadPath = path.join(this.config.baseDir, 'app/public/uploads');
if (!fs.existsSync(uploadPath)) {
fs.mkdirSync(uploadPath);
}
return path.join(uploadPath, uuid.v4() + ext);
}
_beforeSave(stream: any, option: any) {
const { ctx } = this;
const ext = path.extname(stream.filename).toLocaleLowerCase();
if (option.ext) {
if (!_.isArray(option.ext)) {
option.ext = [ option.ext ];
}
let pass = false;
for (let i = 0; i < option.ext.length; i++) {
const r = option.ext[i];
if ('.' + r.toLocaleLowerCase() === ext) {
pass = true;
break;
}
}
if (!pass) {
ctx.throw(400, '文件扩展名不符合要求');
}
}
}
async saveToLocal(option?: any, stream2?: any) {
const { ctx } = this;
option = option || {};
const stream = stream2 || await ctx.getFileStream();
this._beforeSave(stream, option);
const ext = path.extname(stream.filename).toLocaleLowerCase();
const filename = uuid.v4() + ext;
// 文件生成绝对路径
// 当然这里这样市不行的,因为你还要判断一下是否存在文件路径
const uploadPath = path.join(this.config.baseDir, 'app/public/uploads');
if (!fs.existsSync(uploadPath)) {
fs.mkdirSync(uploadPath);
}
const target = path.join(uploadPath, filename);
// 生成一个文件写入 文件流
const writeStream = fs.createWriteStream(target);
try {
// 异步把文件流 写入
await awaitWriteStream(stream.pipe(writeStream));
} catch (err) {
// 如果出现错误,关闭管道
await sendToWormhole(stream);
throw err;
}
// 文件响应
return {
fileName: stream.filename,
url: path.join(uploadPath, filename),
};
}
async compressingAndSaveToOSS({ filename, stream: stream2 }: any) {
console.log('compressingAndSaveToOSS============>', stream2);
const { ctx } = this;
const {
url: path,
} = await ctx.service.file.saveToLocal({}, stream2);
const sourceData = fs.readFileSync(path);
const resultData = await ctx.service.tinyImage.compressing(sourceData);
const Duplex = require('stream').Duplex;
const stream = new Duplex();
stream.push(resultData);
stream.push(null);
stream.filename = filename;
let oss_url;
let url;
try {
const result = await ctx.oss.get('media').put(filename, stream);
oss_url = result.url;
url = `https://r.51gjj.com/${filename}`;
} catch (e) {
ctx.throw(500, e);
}
console.log('compressingAndSaveToOSS============>');
console.log({
fileName: stream.filename,
url,
oss_url,
});
}
async saveToOSS(option: any) {
const { ctx } = this;
option = option || {};
const stream = await ctx.getFileStream();
this._beforeSave(stream, option);
const ext = path.extname(stream.filename).toLocaleLowerCase();
const filename = uuid.v4() + ext;
let oss_url: any;
let url: any;
try {
const result = await ctx.oss.get('default').put(filename, stream);
oss_url = result.url;
url = `oss://${filename}`;
} catch (e) {
ctx.throw(500, e);
}
if (url) {
return {
fileName: stream.filename,
url,
oss_url,
};
}
ctx.throw(500, 'oss upload failed!');
}
async saveToCDN(option?: any, stream?: any) {
const { ctx } = this;
option = option || {};
stream = stream || await ctx.getFileStream();
this._beforeSave(stream, option);
const ext = path.extname(stream.filename).toLocaleLowerCase();
const filename = 'zeus/' + moment().format('YYYYMMDD') + '/' + uuid.v4() + ext;
let oss_url: any;
let url: any;
try {
const result = await ctx.oss.get('media').put(filename, stream);
oss_url = result.url;
url = `https://r.51gjj.com/${filename}`;
} catch (e) {
ctx.throw(500, e);
}
if (url) {
return {
fileName: stream.filename,
url,
oss_url,
};
}
ctx.throw(500, 'oss upload failed!');
}
async saveToDB({ fileName, url }: any) {
const { ctx } = this;
const row = await ctx.model.File.create({
file_name: fileName,
url,
});
return row;
}
}
import { Service, Context } from 'egg';
import Sequelize from 'sequelize';
import moment from 'moment';
const Op = Sequelize.Op;
export default class EstateAppointmentService extends Service {
modelGroupName: string;
modelName: string;
formatConfig: any;
constructor(ctx: Context) {
super(ctx);
this.modelGroupName = 'wafangModel';
this.modelName = 'WafangAppointment';
this.formatConfig = [ '*' ];
}
async findByPage({ page = 1, size = 10 }, where: any = {}, options = {}, raw = false) {
const { ctx, modelGroupName, modelName, formatConfig } = this;
let format: any;
if (!raw) {
const newformatConfig = formatConfig.concat([
{ from: 'project', to: 'project' },
]);
format = newformatConfig;
}
if (where.phone) {
where.phone_hide = {
[Op.like]: `%${where.phone}%`,
};
}
delete where.phone;
if (where.user_id) {
where.user_id = {
[Op.like]: `%${where.user_id}%`,
};
}
if (where.operation_at) {
const at = where.operation_at.split('/');
where.operation_at = {
[Op.gte]: moment(at.shift()).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
[Op.lte]: moment(at.shift()).endOf('day').format('YYYY-MM-DD HH:mm:ss'),
};
}
if (where.created_at) {
const at = where.created_at.split('/');
where.created_at = {
[Op.gte]: moment(at.shift()).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
[Op.lte]: moment(at.shift()).endOf('day').format('YYYY-MM-DD HH:mm:ss'),
};
}
const associate = [{
model: ctx.wafangModel.WafangProject,
as: 'project',
required: false,
where: { valid: 1 },
}];
const data = await ctx[modelGroupName][modelName].findAll({
include: associate,
where: { valid: 1, ...where },
offset: size * (page - 1),
order: [[ 'created_at', 'DESC' ]],
limit: size,
...options,
});
return ctx.service.output.toArray(data, format);
}
async count(where = {}, options = {}) {
const { ctx, modelName, modelGroupName } = this;
const inst = await ctx[modelGroupName][modelName].findOne(
{
attributes: [
[
ctx[modelGroupName].fn('COUNT', ctx[modelGroupName].col('id')), 'total',
],
],
where: { ...where, valid: 1 },
...options,
});
return inst.get('total');
}
async delete(id: number) {
const { ctx, modelName, modelGroupName } = this;
const record = await ctx[modelGroupName][modelName].findOne({
where: {
id,
},
});
if (!record) {
this.ctx.throw(400, '记录不存在');
}
await record.update({ valid: 0 });
return record;
}
async update(id: number, params: any) {
const { ctx, modelGroupName, modelName } = this;
const record = await ctx[modelGroupName][modelName].findOne({
where: {
id,
},
});
if (!record) {
this.ctx.throw(400, '记录不存在');
}
await record.update(params);
return record;
}
}
This diff is collapsed.
import { Service, Context } from 'egg';
import Sequelize from 'sequelize';
const Op = Sequelize.Op;
export default class EstateMaterialService extends Service {
modelGroupName: string;
modelName: string;
formatConfig: any;
constructor(ctx: Context) {
super(ctx);
this.modelGroupName = 'wafangModel';
this.modelName = 'WafangBlock';
this.formatConfig = [ '*' ];
}
async findByPage({ page = 1, size = 10 }, where: any = {}, options = {}, raw = false) {
const { ctx, modelGroupName, modelName, formatConfig } = this;
let format: any;
if (!raw) {
const newformatConfig = formatConfig.concat([
{ from: 'blockContent', to: 'blockContent' },
]);
format = newformatConfig;
}
if (where.title) {
where.title = {
[Op.like]: `%${where.title}%`,
};
}
if (where.alias) {
where.alias = {
[Op.like]: `%${where.alias}%`,
};
}
const associate = [{
model: ctx.wafangModel.WafangBlockContent,
as: 'blockContent',
required: false,
}];
const data = await ctx[modelGroupName][modelName].findAll({
include: associate,
where: { valid: 1, ...where },
offset: size * (page - 1),
order: [[ 'created_at', 'DESC' ]],
limit: size,
...options,
});
return ctx.service.output.toArray(data, format);
}
async count(where = {}, options = {}) {
const { ctx, modelName, modelGroupName } = this;
const inst = await ctx[modelGroupName][modelName].findOne(
{
attributes: [
[
ctx[modelGroupName].fn('COUNT', ctx[modelGroupName].col('id')), 'total',
],
],
where: { ...where, valid: 1 },
...options,
});
return inst.get('total');
}
async update(id: number, params: any) {
const { ctx, modelGroupName, modelName } = this;
const record = await ctx[modelGroupName][modelName].findOne({
where: {
id,
},
});
if (!record) {
this.ctx.throw(400, '记录不存在');
}
let extraData: any = [];
if (params.list) {
extraData = params.list;
}
delete params.list;
await record.update(params);
for (let i = 0; i < extraData.length; i++) {
const item = extraData[i];
if (item.id) {
const itemID = item.id;
delete item.id;
await this.updateContent(itemID, {
...item,
updated_at: new Date(),
});
} else {
await this.createContent({
...item,
block_id: record.id,
created_at: new Date(),
updated_at: new Date(),
});
}
}
return record;
}
async updateContent(id: number, params: any) {
const { ctx, modelGroupName } = this;
const record = await ctx[modelGroupName].WafangBlockContent.findOne({
where: {
id,
},
});
if (!record) {
this.ctx.throw(400, '记录不存在');
}
await record.update(params);
return record;
}
async create(params: any) {
const { ctx, modelGroupName, modelName } = this;
let extraData: any = [];
if (params.list) {
extraData = params.list;
}
delete params.list;
const record = await ctx[modelGroupName][modelName].create(params);
for (let i = 0; i < extraData.length; i++) {
const item = extraData[i];
await this.createContent({
...item,
block_id: record.id,
created_at: new Date(),
updated_at: new Date(),
});
}
return record;
}
async createContent(params: any) {
const { ctx, modelGroupName } = this;
const record = await ctx[modelGroupName].WafangBlockContent.create(params);
return record;
}
async delete(id: number) {
const { ctx, modelName, modelGroupName } = this;
const record = await ctx[modelGroupName][modelName].findOne({
where: {
id,
},
});
if (!record) {
this.ctx.throw(400, '记录不存在');
}
await record.update({ valid: 0 });
return record;
}
}
import { Service, Context } from 'egg';
import Sequelize from 'sequelize';
const Op = Sequelize.Op;
export default class EstateTagService extends Service {
modelGroupName: string;
modelName: string;
formatConfig: any;
constructor(ctx: Context) {
super(ctx);
this.modelGroupName = 'wafangModel';
this.modelName = 'WafangTag';
this.formatConfig = [ '*' ];
}
async findByPage({ page = 1, size = 10 }, where: any = {}, options = {}, raw = false) {
const { ctx, modelGroupName, modelName, formatConfig } = this;
let format: any;
if (!raw) {
format = formatConfig;
}
if (where.tag) {
where.tag = {
[Op.like]: `%${where.tag}%`,
};
}
if (where.type) {
const typeMap: any = {
house: 'is_house',
business: 'is_business',
office: 'is_office',
};
where[typeMap[where.type] || 'is_house'] = 1;
}
delete where.type;
const data = await ctx[modelGroupName][modelName].findAll({
where: { valid: 1, ...where },
offset: size * (page - 1),
order: [[ 'created_at', 'DESC' ]],
limit: size,
...options,
});
return ctx.service.output.toArray(data, format);
}
async count(where = {}, options = {}) {
const { ctx, modelName, modelGroupName } = this;
const inst = await ctx[modelGroupName][modelName].findOne(
{
attributes: [
[
ctx[modelGroupName].fn('COUNT', ctx[modelGroupName].col('id')), 'total',
],
],
where: { ...where, valid: 1 },
...options,
});
return inst.get('total');
}
async findAll() {
const { ctx, modelGroupName, modelName, formatConfig } = this;
const format = formatConfig;
const data = await ctx[modelGroupName][modelName].findAll({
where: { valid: 1 },
});
return ctx.service.output.toArray(data, format);
}
async update(id: number, params: any) {
const { ctx, modelGroupName, modelName } = this;
const record = await ctx[modelGroupName][modelName].findOne({
where: {
id,
},
});
if (!record) {
this.ctx.throw(400, '记录不存在');
}
await record.update(params);
return record;
}
async create(params: any) {
const { ctx, modelGroupName, modelName } = this;
const record = await ctx[modelGroupName][modelName].create(params);
return record;
}
async delete(id: number) {
const { ctx, modelName, modelGroupName } = this;
const record = await ctx[modelGroupName][modelName].findOne({
where: {
id,
},
});
if (!record) {
this.ctx.throw(400, '记录不存在');
}
await record.update({ valid: 0 });
return record;
}
}
import { Service, Context } from 'egg';
import Sequelize from 'sequelize';
const Op = Sequelize.Op;
export default class EstateVideoService extends Service {
modelGroupName: string;
modelName: string;
formatConfig: any;
constructor(ctx: Context) {
super(ctx);
this.modelGroupName = 'wafangModel';
this.modelName = 'WafangVideo';
this.formatConfig = [ '*' ];
}
async findByPage({ page = 1, size = 10 }, where: any = {}, options = {}, raw = false) {
const { ctx, modelGroupName, modelName, formatConfig } = this;
let format: any;
if (!raw) {
const newformatConfig = formatConfig.concat([
{ from: 'project', to: 'project' },
]);
format = newformatConfig;
}
if (where.title) {
where.title = {
[Op.like]: `%${where.title}%`,
};
}
const projectWhere: any = {};
if (where.project_name) {
projectWhere.name = {
[Op.like]: `%${where.project_name}%`,
};
}
delete where.project_name;
const associate = [{
model: ctx.wafangModel.WafangProject,
as: 'project',
required: false,
where: { valid: 1, ...projectWhere },
}];
const data = await ctx[modelGroupName][modelName].findAll({
include: associate,
where: { valid: 1, ...where },
offset: size * (page - 1),
order: [[ 'created_at', 'DESC' ]],
limit: size,
...options,
});
return ctx.service.output.toArray(data, format);
}
async count(where = {}, options = {}) {
const { ctx, modelName, modelGroupName } = this;
const inst = await ctx[modelGroupName][modelName].findOne(
{
attributes: [
[
ctx[modelGroupName].fn('COUNT', ctx[modelGroupName].col('id')), 'total',
],
],
where: { ...where, valid: 1 },
...options,
});
return inst.get('total');
}
async update(id: number, params: any) {
const { ctx, modelGroupName, modelName } = this;
const record = await ctx[modelGroupName][modelName].findOne({
where: {
id,
},
});
if (!record) {
this.ctx.throw(400, '记录不存在');
}
await record.update(params);
return record;
}
async create(params: any) {
const { ctx, modelGroupName, modelName } = this;
const record = await ctx[modelGroupName][modelName].create(params);
return record;
}
async delete(id: number) {
const { ctx, modelName, modelGroupName } = this;
const record = await ctx[modelGroupName][modelName].findOne({
where: {
id,
},
});
if (!record) {
this.ctx.throw(400, '记录不存在');
}
await record.update({ valid: 0 });
return record;
}
}
environment:
matrix:
- nodejs_version: '8'
install:
- ps: Install-Product node $env:nodejs_version
- npm i npminstall && node_modules\.bin\npminstall
test_script:
- node --version
- npm --version
- npm run test
build: off
export interface ConfigDefaultState {
multipart?: { fileExtensions: string[]; fileSize: string; };
projectRootPath?: string;
keys?: string;
middleware?: any;
jwtMiddle?: { exclude: string[]; };
jwt?: any;
security?: any;
cors?: any;
DD_CONFIG?: any;
oss?: any;
onerror?: any;
mailer?: any;
}
export default (appInfo: { name: string; }) => {
const config: ConfigDefaultState = {};
config.multipart = {
fileExtensions: [
'.xlsx',
'.xls',
],
fileSize: '100mb',
};
config.projectRootPath = '/api';
// use for cookie sign key, should change to your own and keep security
config.keys = appInfo.name + '_1537508791174_5973';
// add your config here
config.middleware = [
'errorHandler',
'pagination',
'jwtMiddle',
];
config.jwtMiddle = {
exclude: [
'/api/login',
'/api/upload/**',
'/api/estate/**',
],
};
config.jwt = {
secret: '123456',
};
// 是否启用csrf安全
config.security = {
csrf: {
enable: false,
},
domainWhiteList: [],
};
config.cors = {
origin: '*',
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS',
credentials: true,
};
config.DD_CONFIG = {
corpId: 'dingbd41a9a5fb105629', // 公司唯一标识
secret: '5gz8ZKXwNWqiIjUeemVI1Zxnw7s6PJx6KJLOpabexuZhCSYREloULpNMUlWGJEQm', // 认证密钥,非常重要,妥善保管,具有极高权限
token: 'dsrtiw', // 加解密时有用到
aesKey: '1234567890123456789012345678901234567890123', // 加解密时有用到
agentId: {
// 微应用的ID
default: '116146340',
},
nonceStr: 'gjjj51', // aes加解密及签名时用到, len=6
sso: {
appId: 'dingoameqb6kx8gwcg50wi', // 钉钉扫码登陆的 appId
appSecret: 'aCep7Y_Tr5x6ISr-UTML88pBN5KBqzhxSJ9FwA-emTq_GJaWZCRA-P1thcd2K-0E', // 钉钉扫码登陆的 appSecret
},
};
config.oss = {
clients: {
default: {
accessKeyId: 'LTAIRRW7SbrqKUIJ',
accessKeySecret: 'AC15o0PrchUiK1xlZx7DhF5Bmumt7K',
bucket: 'nodeadmin',
endpoint: 'oss-cn-hangzhou.aliyuncs.com',
timeout: '60s',
},
media: {
accessKeyId: 'LTAIA1esjBRaabT9',
accessKeySecret: '2Bf7tp5Dkr657ZzUv1bfwbEgl1zQT4',
bucket: '51shequ',
endpoint: 'oss-cn-hangzhou.aliyuncs.com',
timeout: '60s',
},
},
};
config.onerror = {
appErrorFilter: err => {
if (err.status && Math.floor(err.status / 100) === 4) {
return false;
}
return true;
},
};
config.mailer = {
client: {
host: 'smtp.exmail.qq.com',
port: 465,
user: 'report@jianbing.com', // generated ethereal user
pass: 'AAbb1234', // generated ethereal password
sender: '煎饼网络数据中心--测试',
},
};
return config;
};
export interface ConfigLocalState {
debug?: boolean;
sessionExpireIn?: number;
jwt?: any;
sequelize?: any;
redis?: any;
SERVICE_HOST?: string;
runEnv?: string;
proxy?: any;
}
export default () => {
const config: ConfigLocalState = {};
config.debug = true;
config.sessionExpireIn = 60 * 60 * 24 * 7 * 10000;
config.jwt = {
secret: '51e69bd6-7fdb-4a76-ac83-0d32b6f09723',
exp: Math.floor(Date.now() / 1000) + config.sessionExpireIn, // 一周
enable: true, // default is false
credentialsRequired: false,
match: '*', // optional
};
config.sequelize = {
datasources: [{
// 东八时区
timezone: '+08:00',
delegate: 'model',
baseDir: 'model/default',
dialect: 'mysql',
host: 'rm-bp1mnwmta5778y0d3jo.mysql.rds.aliyuncs.com',
database: 'htadmin_dev',
username: 'htadmin',
password: 'gSI6z8qR5B9dZ464h3S1g',
port: 3306,
}, {
// 东八时区
timezone: '+08:00',
delegate: 'wafangModel',
baseDir: 'model/wafang',
dialect: 'mysql',
host: 'rm-wafang-rds-pubilc.mysql.rds.aliyuncs.com',
database: 'wafang_dev',
username: 'wafang_test',
password: 'ztQ4Y8piePY3521gx74Ii',
port: 3306,
}],
};
config.redis = {
clients: {
zeus: {
port: 6379,
host: '116.62.55.137',
password: 'DEV8redis',
db: 0,
},
demeter: {
port: 6379,
host: '116.62.55.137',
password: 'DEV8redis',
db: 0,
},
},
};
config.SERVICE_HOST = 'https://dev-nginx.jianbing.com';
config.runEnv = 'uat';
config.proxy = {
match: /(\/api\/yizhi\/)/, // path pattern.
proxyList: [{
match: /(\/api\/yizhi\/)/,
host: config.SERVICE_HOST, // target host that matched path will be proxy to
map(path) { return path.replace(/(\/api\/yizhi)/, '/yizhi_server/api'); }, // 预处理路径
suppressRequestHeaders: [], // 不需要转发的请求头
}],
};
return config;
};
export interface ConfigProdState {
debug?: boolean;
sessionExpireIn?: number;
logger?: any;
jwt?: any;
sequelize?: any;
redis?: any;
mailer?: any;
SERVICE_HOST?: string;
runEnv?: string;
proxy?: any;
}
export default () => {
const config: ConfigProdState = {};
config.debug = true;
config.sessionExpireIn = 60 * 60 * 24 * 7;
config.logger = {
dir: '/jianbing/logs/zeus-server',
};
config.jwt = {
secret: process.env.JWT_SECRET,
exp: Math.floor(Date.now() / 1000) + config.sessionExpireIn,
enable: true, // default is false
credentialsRequired: false,
match: '*', // optional
};
config.sequelize = {
datasources: [{
// 东八时区
timezone: '+08:00',
delegate: 'model',
baseDir: 'model/default',
// other sequelize configurations
username: process.env.MYSQL_USER,
dialect: 'mysql',
host: process.env.MYSQL_HOST,
port: 3306,
database: process.env.MYSQL_DB_NAME,
password: process.env.MYSQL_PWD,
}],
};
config.redis = {
clients: {
zeus: {
port: 6379,
host: process.env.REDIS_HOST,
password: process.env.REDIS_PWD,
db: 0,
},
demeter: { // 理财
port: 6379,
host: process.env.REDIS_DEM_HOST,
password: process.env.REDIS_DEM_PWD,
db: 0,
},
},
};
config.mailer = {
client: {
host: 'smtp.exmail.qq.com',
port: 465,
user: 'report@jianbing.com',
pass: 'AAbb1234',
sender: '煎饼网络数据中心',
},
};
config.SERVICE_HOST = process.env.SERVICE_HOST;
config.runEnv = process.env.runEnv || 'pro';
config.proxy = {
match: /(\/api\/yizhi\/)/, // path pattern.
proxyList: [{
match: /(\/api\/yizhi\/)/,
host: config.SERVICE_HOST, // target host that matched path will be proxy to
map(path) { return path.replace(/(\/api\/yizhi)/, '/yizhi_server/api'); }, // 预处理路径
suppressRequestHeaders: [], // 不需要转发的请求头
}],
};
return config;
};
const path = require('path');
export default {
// had enabled by egg
static: {
enable: true,
},
jwt: {
enable: true,
package: 'egg-jwt',
},
cors: {
enable: true,
package: 'egg-cors',
},
validate: {
enable: true,
package: 'egg-validate',
},
dd: {
enable: true,
// package: 'egg-dd-sdk',
path: path.join(__dirname, '../lib/plugin/egg-dd-sdk'),
},
mysql: {
enable: false,
package: 'egg-mysql',
},
sequelize: {
enable: true,
package: 'egg-sequelize',
},
redis: {
enable: true,
package: 'egg-redis',
},
onerror: {
enable: true,
package: 'egg-onerror',
},
routerPlus: {
enable: true,
package: 'egg-router-plus',
},
oss: {
enable: true,
package: 'egg-oss',
},
mailer: {
enable: true,
path: path.join(__dirname, '../lib/plugin/egg-mailer'),
},
proxy: {
enable: true,
path: path.join(__dirname, '../lib/plugin/egg-proxy'),
},
};
## 1.1.0
* 增加uniconid换取userid接口
## 1.0.0
* 改为async/await
## 0.2.0
* 增加 `service.department.getDepartmentFullPathByUser()`
* 增加 `service.department.getDepartmentFullPathByDepartment()`
## 0.1.0
* 增加`ddMedia` service
* 增加`ddSpace` service
# 关于
> API文档请参见:https://cathayjs.github.io/egg-dd-sdk/
由于钉钉未提供nodejs sdk,初次用nodejs对应相应接口时比较费时费力,所以将项目中的钉钉对接接口整理出来,以`egg插件`的方式提供呈现。
本sdk的主要达到三个目的:
1. config: 钉钉相关配置的约定
2. utils: 将钉钉加解密之类的复杂方法抽象为工具类
3. service: 将与钉钉相关的API接口进行封装,以service方式提供,方便调用
另外,关于为什么基于`egg插件`,说明一下原因:
1. `egg`框架本身在企业级应用框架中的分层非常清晰,扩展机制极其灵活,配套完整,极力推荐
2. 通过`egg插件`的组织,能够非常方便的组织钉钉的配置文件管理、工具使用及service使用
1. 提供了统一的配置管理
2. 提供统一的工具调用方式
3. 提供统一的service调用方式
4. 提供统一的日志服务
> 自己参与的项目非ISV类型,未针对ISV做设计,有需求的同学可以一起参与完善。
>
> API文档请参见:https://cathayjs.github.io/egg-dd-sdk/
## egg 配置说明
package.json
```json
{
"dependecies": {
"egg-dd-sdk": "0.1.4"
}
}
```
config/config.default.js
```js
module.exports.DD_CONFIG = {
corpId: "dingdcf94075751f540635c2f4657eb6378f",
secret: "C-uQKbuaA1zrne3ni2fwBfifMir9h4MEQTIrRi2LoQiE68LdxIWhBqnFxKLYABWT",
token: '123456', // 加解密时有用到
aesKey: "1234567890123456789012345678901234567890123", // 加解密时有用到
agentId: {
'default': '116146340'
},
nonceStr: "123456",
sso: {
appId: 'dingoa9l870sdqembng3je',
appSecret: 'h0Y1uH4w4nkToIvzJzd6VKRNbJsqevOi791B0eeOVM87GrumW4xLEGOQqjzmo9eK'
}
};
```
config/plugin.js
```js
exports.dd = {
enable: true,
package: 'egg-dd-sdk',
};
```
## NEXT
此目录为所有[钉钉官方服务端开发文档](https://open-doc.dingtalk.com/docs/doc.htm?spm=a219a.7629140.0.0.8cqfMW&treeId=385&articleId=104981&docType=1)的目录结构,打钩的是实现的:
- [x] 建立连接
- [x] 免登授权
- [x] 企业应用中调用免登
- [x] 普通钉钉用户账号开放及免登
- [x] 网站应用钉钉扫码登录开发指南
- [ ] 微应用后台管理员免登
- [ ] ISV应用中调用免登
- [x] 通讯录管理
- [x] 人员管理
- [x] 部门管理
- [ ] 权限管理
- [ ] 角色管理
- [ ] 微应用管理
- [x] 消息会话管理
- [x] 普通会话消息
- [ ] 群会话消息
- [x] 文件管理
- [x] 多媒体文件管理
- [x] 钉盘
- [x] 单步文件上传
- [x] 发送文件给指定用户
- [ ] 文件事务
- [ ] 其他
- [x] 智能办公
- [x] 审批
- [ ] 考勤
- [ ] 签到
- [ ] 外部联系人管理
- [ ] 群机器人
- [x] 服务端加密、解密
- [x] js接口API
## 关于单测
`egg-dd-sdk`虽然是egg插件,但有完善的单测机制,且与egg框架目录结构使用方式一致:
* cnpm install
* npm run dev
目前以公共19个用例,覆盖了大部分接口
\ No newline at end of file
{
"_from": "egg-dd-sdk@^1.1.1",
"_id": "egg-dd-sdk@1.1.1",
"_inBundle": false,
"_integrity": "sha512-OAtaPUNp2LFR+GWFGN3IHAYBrg8Fhiga4lZij2AeJrvAqmVIwhtKe/kEOUBd2rOJmuXQePPtCCFEGliJx+DdHQ==",
"_location": "/egg-dd-sdk",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "egg-dd-sdk@^1.1.1",
"name": "egg-dd-sdk",
"escapedName": "egg-dd-sdk",
"rawSpec": "^1.1.1",
"saveSpec": null,
"fetchSpec": "^1.1.1"
},
"_requiredBy": [
"/"
],
"_resolved": "https://registry.npmjs.org/egg-dd-sdk/-/egg-dd-sdk-1.1.1.tgz",
"_shasum": "532e1cd88312aa64fc0dd66984b78c0def97ac04",
"_spec": "egg-dd-sdk@^1.1.1",
"_where": "/Users/haywael/corp/zeus/zeus-server",
"author": {
"name": "johnnychq",
"email": "johnnychq@gmail.com"
},
"bugs": {
"url": "https://github.com/cathayjs/egg-dd-sdk/issues"
},
"bundleDependencies": false,
"dependencies": {
"egg": "^2.7.1",
"formstream": "^1.1.0",
"md5": "^2.2.1",
"nodejs-dd-aes": "^0.0.3"
},
"deprecated": false,
"description": "> API文档请参见:https://cathayjs.github.io/egg-dd-sdk/",
"devDependencies": {
"atool-build": "^0.9.0",
"autod": "^2.7.1",
"cheerio": "^0.22.0",
"egg-bin": "^3.7.0",
"egg-mock": "^3.2.0",
"eslint": "^3.10.2",
"eslint-config-airbnb": "^9.0.1",
"eslint-config-egg": "^3.2.0",
"eslint-loader": "^1.6.1",
"eslint-plugin-import": "^1.8.1",
"eslint-plugin-jsx-a11y": "^1.4.2",
"expect": "^1.20.1",
"intelli-espower-loader": "^1.0.1",
"jstransform": "^11.0.3",
"mochawesome": "^2.0.4",
"mockjs": "^1.0.1-beta3",
"power-assert": "^1.4.2",
"pre-commit": "1.x",
"supertest": "^2.0.1"
},
"eggPlugin": {},
"homepage": "https://github.com/cathayjs/egg-dd-sdk#readme",
"license": "MIT",
"name": "egg-dd-sdk",
"repository": {
"type": "git",
"url": "git+https://github.com/cathayjs/egg-dd-sdk.git"
},
"scripts": {
"test": "egg-bin test"
},
"version": "1.1.1"
}
'use strict';
module.exports = {
write: true,
prefix: '^',
plugin: 'autod-egg',
test: [
'test',
'benchmark',
],
devdep: [
'egg',
'egg-ci',
'egg-bin',
'autod',
'autod-egg',
'eslint',
'eslint-config-egg',
'webstorm-disable-index',
],
exclude: [
'./test/fixtures',
'./docs',
'./coverage',
],
};
{
"extends": "eslint-config-egg"
}
<!--
Thank you for your pull request. Please review below requirements.
Bug fixes and new features should include tests and possibly benchmarks.
Contributors guide: https://github.com/eggjs/egg/blob/master/CONTRIBUTING.md
感谢您贡献代码。请确认下列 checklist 的完成情况。
Bug 修复和新功能必须包含测试,必要时请附上性能测试。
Contributors guide: https://github.com/eggjs/egg/blob/master/CONTRIBUTING.md
-->
##### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->
- [ ] `npm test` passes
- [ ] tests and/or benchmarks are included
- [ ] documentation is changed or added
- [ ] commit message follows commit guidelines
##### Affected core subsystem(s)
<!-- Provide affected core subsystem(s). -->
##### Description of change
<!-- Provide a description of the change below this comment. -->
logs/
npm-debug.log
node_modules/
coverage/
.idea/
run/
.DS_Store
*.swp
The MIT License (MIT)
Copyright (c) 2017 Alibaba Group Holding Limited and other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# egg-mailer
[![NPM version][npm-image]][npm-url]
[![build status][travis-image]][travis-url]
[![Test coverage][codecov-image]][codecov-url]
[![David deps][david-image]][david-url]
[![Known Vulnerabilities][snyk-image]][snyk-url]
[![npm download][download-image]][download-url]
[npm-image]: https://img.shields.io/npm/v/egg-mailer.svg?style=flat-square
[npm-url]: https://npmjs.org/package/egg-mailer
[travis-image]: https://img.shields.io/travis/eggjs/egg-mailer.svg?style=flat-square
[travis-url]: https://travis-ci.org/eggjs/egg-mailer
[codecov-image]: https://img.shields.io/codecov/c/github/eggjs/egg-mailer.svg?style=flat-square
[codecov-url]: https://codecov.io/github/eggjs/egg-mailer?branch=master
[david-image]: https://img.shields.io/david/eggjs/egg-mailer.svg?style=flat-square
[david-url]: https://david-dm.org/eggjs/egg-mailer
[snyk-image]: https://snyk.io/test/npm/egg-mailer/badge.svg?style=flat-square
[snyk-url]: https://snyk.io/test/npm/egg-mailer
[download-image]: https://img.shields.io/npm/dm/egg-mailer.svg?style=flat-square
[download-url]: https://npmjs.org/package/egg-mailer
<!--
Description here.
-->
## Install
```bash
$ npm i egg-mailer --save
```
## Usage
```js
// {app_root}/config/plugin.js
exports.mailer = {
enable: true,
package: 'egg-mailer',
};
```
## Configuration
```js
// {app_root}/config/config.default.js
exports.mailer = {
};
```
see [config/config.default.js](config/config.default.js) for more detail.
## Example
<!-- example here -->
## Questions & Suggestions
Please open an issue [here](https://github.com/eggjs/egg/issues).
## License
[MIT](LICENSE)
# egg-mailer
[![NPM version][npm-image]][npm-url]
[![build status][travis-image]][travis-url]
[![Test coverage][codecov-image]][codecov-url]
[![David deps][david-image]][david-url]
[![Known Vulnerabilities][snyk-image]][snyk-url]
[![npm download][download-image]][download-url]
[npm-image]: https://img.shields.io/npm/v/egg-mailer.svg?style=flat-square
[npm-url]: https://npmjs.org/package/egg-mailer
[travis-image]: https://img.shields.io/travis/eggjs/egg-mailer.svg?style=flat-square
[travis-url]: https://travis-ci.org/eggjs/egg-mailer
[codecov-image]: https://img.shields.io/codecov/c/github/eggjs/egg-mailer.svg?style=flat-square
[codecov-url]: https://codecov.io/github/eggjs/egg-mailer?branch=master
[david-image]: https://img.shields.io/david/eggjs/egg-mailer.svg?style=flat-square
[david-url]: https://david-dm.org/eggjs/egg-mailer
[snyk-image]: https://snyk.io/test/npm/egg-mailer/badge.svg?style=flat-square
[snyk-url]: https://snyk.io/test/npm/egg-mailer
[download-image]: https://img.shields.io/npm/dm/egg-mailer.svg?style=flat-square
[download-url]: https://npmjs.org/package/egg-mailer
<!--
Description here.
-->
## 依赖说明
### 依赖的 egg 版本
egg-mailer 版本 | egg 1.x
--- | ---
1.x | 😁
0.x | ❌
### 依赖的插件
<!--
如果有依赖其它插件,请在这里特别说明。如
- security
- multipart
-->
## 开启插件
```js
// config/plugin.js
exports.mailer = {
enable: true,
package: 'egg-mailer',
};
```
## 使用场景
- Why and What: 描述为什么会有这个插件,它主要在完成一件什么事情。
尽可能描述详细。
- How: 描述这个插件是怎样使用的,具体的示例代码,甚至提供一个完整的示例,并给出链接。
## 详细配置
请到 [config/config.default.js](config/config.default.js) 查看详细配置项说明。
## 单元测试
<!-- 描述如何在单元测试中使用此插件,例如 schedule 如何触发。无则省略。-->
## 提问交流
请到 [egg issues](https://github.com/eggjs/egg/issues) 异步交流。
## License
[MIT](LICENSE)
{
"name": "egg-mailer",
"version": "1.0.0",
"description": "mail sender",
"eggPlugin": {
"name": "mailer"
},
"keywords": [
"egg",
"eggPlugin",
"egg-plugin"
],
"dependencies": {},
"devDependencies": {
"autod": "^3.0.0",
"autod-egg": "^1.0.0",
"egg": "^2.0.0",
"egg-bin": "^4.3.0",
"egg-ci": "^1.8.0",
"egg-mock": "^3.13.0",
"eslint": "^4.11.0",
"eslint-config-egg": "^5.1.0",
"webstorm-disable-index": "^1.2.0",
"nodemailer": "^4.6.8"
},
"engines": {
"node": ">=8.0.0"
},
"scripts": {
"test": "npm run lint -- --fix && egg-bin pkgfiles && npm run test-local",
"test-local": "egg-bin test",
"cov": "egg-bin cov",
"lint": "eslint .",
"ci": "egg-bin pkgfiles --check && npm run lint && npm run cov",
"pkgfiles": "egg-bin pkgfiles",
"autod": "autod"
},
"files": [
"app.js",
"agent.js",
"config"
],
"ci": {
"version": "8, 9"
},
"repository": {
"type": "git",
"url": "git+https://github.com/eggjs/egg-mailer.git"
},
"bugs": {
"url": "https://github.com/eggjs/egg/issues"
},
"homepage": "https://github.com/eggjs/egg-mailer#readme",
"author": "weiyuqun",
"license": "MIT"
}
{
"name": "mailer-test",
"version": "0.0.1"
}
\ No newline at end of file
The MIT License (MIT)
Copyright (c) 2017 Alibaba Group Holding Limited and other contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# egg-proxy
<!--
Description here.
-->
request proxy plugin for egg framework. based on [koa-proxy](https://github.com/popomore/koa-proxy)
## Install
```bash
$ npm i egg-proxy --save
```
## Usage
```js
// {app_root}/config/plugin.js
exports.proxy = {
enable: true,
package: 'egg-proxy',
};
```
## Configuration
```js
// {app_root}/config/config.default.js
exports.proxy = {
host: 'http://localhost:9000', // target host that matched path will be proxy to
match: /\/assets/ // path pattern.
};
```
see [config/config.default.js](config/config.default.js) for more detail.
## Example
<!-- example here -->
## Questions & Suggestions
Please open an issue [here](https://github.com/xyeric/egg-proxy/issues).
## License
[MIT](LICENSE)
# egg-proxy
[![NPM version][npm-image]][npm-url]
[![build status][travis-image]][travis-url]
[![Test coverage][codecov-image]][codecov-url]
[![David deps][david-image]][david-url]
[![Known Vulnerabilities][snyk-image]][snyk-url]
[![npm download][download-image]][download-url]
[npm-image]: https://img.shields.io/npm/v/egg-proxy.svg?style=flat-square
[npm-url]: https://npmjs.org/package/egg-proxy
[travis-image]: https://img.shields.io/travis/eggjs/egg-proxy.svg?style=flat-square
[travis-url]: https://travis-ci.org/eggjs/egg-proxy
[codecov-image]: https://img.shields.io/codecov/c/github/eggjs/egg-proxy.svg?style=flat-square
[codecov-url]: https://codecov.io/github/eggjs/egg-proxy?branch=master
[david-image]: https://img.shields.io/david/eggjs/egg-proxy.svg?style=flat-square
[david-url]: https://david-dm.org/eggjs/egg-proxy
[snyk-image]: https://snyk.io/test/npm/egg-proxy/badge.svg?style=flat-square
[snyk-url]: https://snyk.io/test/npm/egg-proxy
[download-image]: https://img.shields.io/npm/dm/egg-proxy.svg?style=flat-square
[download-url]: https://npmjs.org/package/egg-proxy
<!--
Description here.
-->
## 依赖说明
### 依赖的 egg 版本
egg-proxy 版本 | egg 1.x
--- | ---
1.x | 😁
0.x | ❌
### 依赖的插件
<!--
如果有依赖其它插件,请在这里特别说明。如
- security
- multipart
-->
## 开启插件
```js
// config/plugin.js
exports.proxy = {
enable: true,
package: 'egg-proxy',
};
```
## 使用场景
- Why and What: 描述为什么会有这个插件,它主要在完成一件什么事情。
尽可能描述详细。
- How: 描述这个插件是怎样使用的,具体的示例代码,甚至提供一个完整的示例,并给出链接。
## 详细配置
请到 [config/config.default.js](config/config.default.js) 查看详细配置项说明。
## 单元测试
<!-- 描述如何在单元测试中使用此插件,例如 schedule 如何触发。无则省略。-->
## 提问交流
请到 [egg issues](https://github.com/xyeric/egg-proxy/issues) 异步交流。
## License
[MIT](LICENSE)
{
"_from": "egg-proxy",
"_id": "egg-proxy@1.1.0",
"_inBundle": false,
"_integrity": "sha512-D8M1DWy0k9nUtTeZ7ERY2RIaFT8Loe/6dx9gr4gjm9fkzC3d4FF2Kc5xD8PCfGDIsKLao+r67ulViKVe4kqlXg==",
"_location": "/egg-proxy",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "egg-proxy",
"name": "egg-proxy",
"escapedName": "egg-proxy",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/egg-proxy/-/egg-proxy-1.1.0.tgz",
"_shasum": "c3a01e8e8b20633b4df4686fbff738feb70be8ec",
"_spec": "egg-proxy",
"_where": "/Users/haywael/corp/zeus/zeus-server",
"author": {
"name": "xyeric"
},
"bugs": {
"url": "https://github.com/xyeric/egg-proxy/issues"
},
"bundleDependencies": false,
"ci": {
"version": "6, 8"
},
"dependencies": {
},
"deprecated": false,
"description": "proxy plugin for egg",
"devDependencies": {
"autod": "^2.8.0",
"autod-egg": "^1.0.0",
"egg": "^1.4.0",
"egg-bin": "^3.4.0",
"egg-ci": "^1.6.0",
"egg-mock": "^3.7.0",
"eslint": "^3.19.0",
"eslint-config-egg": "^4.2.0",
"supertest": "^3.0.0",
"webstorm-disable-index": "^1.2.0"
},
"eggPlugin": {
"name": "proxy"
},
"engines": {
"node": ">=6.0.0"
},
"files": [
"app",
"lib",
"config",
"app.js"
],
"homepage": "https://github.com/xyeric/egg-proxy#readme",
"keywords": [
"egg",
"eggPlugin",
"egg-plugin",
"proxy",
"egg-proxy"
],
"license": "MIT",
"name": "egg-proxy",
"repository": {
"type": "git",
"url": "git+https://github.com/xyeric/egg-proxy.git"
},
"scripts": {
"autod": "autod",
"ci": "egg-bin pkgfiles --check && npm run lint && npm run cov",
"cov": "egg-bin cov",
"lint": "eslint .",
"pkgfiles": "egg-bin pkgfiles",
"test": "npm run lint -- --fix && egg-bin pkgfiles && npm run test-local",
"test-local": "egg-bin test"
},
"version": "1.1.0"
}
{
"name": "zeus-server",
"version": "1.0.0",
"description": "",
"private": true,
"egg": {
"typescript": true,
"declarations": true
},
"scripts": {
"start": "egg-scripts start --daemon --title=egg-server-zeus-server",
"stop": "egg-scripts stop --title=egg-server-zeus-server",
"dev": "egg-bin dev",
"debug": "egg-bin debug",
"test-local": "egg-bin test",
"test": "npm run lint -- --fix && npm run test-local",
"cov": "egg-bin cov",
"tsc": "ets && tsc -p tsconfig.json",
"ci": "npm run lint && npm run cov && npm run tsc",
"autod": "autod",
"lint": "eslint . --ext .ts",
"clean": "ets clean"
},
"dependencies": {
"@types/node-uuid": "0.0.28",
"await-stream-ready": "^1.0.1",
"content-disposition": "^0.5.3",
"crypto-js": "^4.0.0",
"dataloader": "^2.0.0",
"egg": "^2.6.1",
"egg-cors": "^2.2.3",
"egg-dd-sdk": "^1.1.1",
"egg-jwt": "^3.1.7",
"egg-mysql": "^3.0.0",
"egg-onerror": "^2.1.0",
"egg-oss": "^2.0.0",
"egg-redis": "^2.4.0",
"egg-router-plus": "^1.3.1",
"egg-scripts": "^2.6.0",
"egg-sequelize": "^5.2.2",
"egg-validate": "^2.0.2",
"exceljs": "^4.1.1",
"moment": "^2.27.0",
"mysql2": "^2.1.0",
"node-cache": "^5.1.2",
"node-pinyin": "^0.2.3",
"node-uuid": "^1.4.8",
"node-xlsx": "^0.15.0",
"nodemailer": "^6.4.11",
"qr-image": "^3.2.0",
"tinify": "^1.6.0-beta.2",
"uuid": "^8.3.0",
"xml2js": "^0.4.23"
},
"devDependencies": {
"@types/lodash": "^4.14.159",
"@types/mocha": "^2.2.40",
"@types/node": "^7.0.12",
"@types/supertest": "^2.0.0",
"@types/uuid": "^8.3.0",
"autod": "^3.0.1",
"autod-egg": "^1.1.0",
"egg-bin": "^4.11.0",
"egg-ci": "^1.8.0",
"egg-mock": "^3.16.0",
"eslint": "^6.7.2",
"eslint-config-egg": "^8.0.0",
"tslib": "^1.9.0",
"typescript": "^3.0.0",
"webstorm-disable-index": "^1.2.0"
},
"engines": {
"node": ">=8.9.0"
},
"ci": {
"version": "8"
},
"repository": {
"type": "git",
"url": ""
},
"eslintIgnore": [
"coverage"
],
"author": "",
"license": "MIT"
}
import * as assert from 'assert';
import { app } from 'egg-mock/bootstrap';
describe('test/app/controller/home.test.ts', () => {
it('should GET /', async () => {
const result = await app.httpRequest().get('/').expect(200);
assert(result.text === 'hi, egg');
});
});
import * as assert from 'assert';
import { Context } from 'egg';
import { app } from 'egg-mock/bootstrap';
describe('test/app/service/Test.test.js', () => {
let ctx: Context;
before(async () => {
ctx = app.mockContext();
});
it('sayHi', async () => {
const result = await ctx.service.test.sayHi('egg');
assert(result === 'hi, egg');
});
});
{
"compileOnSave": true,
"compilerOptions": {
"target": "es2017",
"module": "commonjs",
"strict": true,
"noImplicitAny": false,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"charset": "utf8",
"allowJs": false,
"pretty": true,
"noEmitOnError": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"strictPropertyInitialization": false,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"inlineSourceMap": true,
"importHelpers": true,
"esModuleInterop": true,
},
"exclude": [
"app/public",
"app/views",
"node_modules*"
]
}
// This file is created by egg-ts-helper@1.25.8
// Do not modify this file!!!!!!!!!
import 'egg';
import ExportUtil from '../../../app/controller/util';
import ExportMpEstateAppointment from '../../../app/controller/mpEstate/appointment';
import ExportMpEstateList from '../../../app/controller/mpEstate/list';
import ExportMpEstateMaterial from '../../../app/controller/mpEstate/material';
import ExportMpEstateTag from '../../../app/controller/mpEstate/tag';
import ExportMpEstateVideo from '../../../app/controller/mpEstate/video';
import ExportUserIndex from '../../../app/controller/user/index';
declare module 'egg' {
interface IController {
util: ExportUtil;
mpEstate: {
appointment: ExportMpEstateAppointment;
list: ExportMpEstateList;
material: ExportMpEstateMaterial;
tag: ExportMpEstateTag;
video: ExportMpEstateVideo;
}
user: {
index: ExportUserIndex;
}
}
}
// This file is created by egg-ts-helper@1.25.8
// Do not modify this file!!!!!!!!!
import 'egg';
import ExtendApplication from '../../../app/extend/application';
type ExtendApplicationType = typeof ExtendApplication;
declare module 'egg' {
interface Application extends ExtendApplicationType { }
}
\ No newline at end of file
// This file is created by egg-ts-helper@1.25.8
// Do not modify this file!!!!!!!!!
import 'egg';
import ExtendContext from '../../../app/extend/context';
type ExtendContextType = typeof ExtendContext;
declare module 'egg' {
interface Context extends ExtendContextType { }
}
\ No newline at end of file
// This file is created by egg-ts-helper@1.25.8
// Do not modify this file!!!!!!!!!
import 'egg';
import ExtendIHelper from '../../../app/extend/helper';
type ExtendIHelperType = typeof ExtendIHelper;
declare module 'egg' {
interface IHelper extends ExtendIHelperType { }
}
\ No newline at end of file
// This file is created by egg-ts-helper@1.25.8
// Do not modify this file!!!!!!!!!
import 'egg';
import ExtendRequest from '../../../app/extend/request';
type ExtendRequestType = typeof ExtendRequest;
declare module 'egg' {
interface Request extends ExtendRequestType { }
}
\ No newline at end of file
// This file is created by egg-ts-helper@1.25.8
// Do not modify this file!!!!!!!!!
import 'egg';
export * from 'egg';
export as namespace Egg;
// This file is created by egg-ts-helper@1.25.8
// Do not modify this file!!!!!!!!!
import 'egg';
import ExportErrorHandler from '../../../app/middleware/errorHandler';
import ExportJwtMiddle from '../../../app/middleware/jwtMiddle';
import ExportPagination from '../../../app/middleware/pagination';
declare module 'egg' {
interface IMiddleware {
errorHandler: typeof ExportErrorHandler;
jwtMiddle: typeof ExportJwtMiddle;
pagination: typeof ExportPagination;
}
}
// 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>;
}
}
}
// This file is created by egg-ts-helper@1.25.8
// Do not modify this file!!!!!!!!!
import 'egg';
type AnyClass = new (...args: any[]) => any;
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';
import ExportMpEstateTag from '../../../app/service/mpEstate/tag';
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>;
material: AutoInstanceType<typeof ExportMpEstateMaterial>;
tag: AutoInstanceType<typeof ExportMpEstateTag>;
video: AutoInstanceType<typeof ExportMpEstateVideo>;
}
}
}
// This file is created by egg-ts-helper@1.25.8
// Do not modify this file!!!!!!!!!
import 'egg';
import 'egg-onerror';
import 'egg-session';
import 'egg-i18n';
import 'egg-watcher';
import 'egg-multipart';
import 'egg-security';
import 'egg-development';
import 'egg-logrotator';
import 'egg-schedule';
import 'egg-static';
import 'egg-jsonp';
import 'egg-view';
import 'egg-jwt';
import 'egg-cors';
import 'egg-validate';
import 'egg-sequelize';
import 'egg-redis';
import 'egg-router-plus';
import 'egg-oss';
import { EggPluginItem } from 'egg';
declare module 'egg' {
interface EggPlugin {
onerror?: EggPluginItem;
session?: EggPluginItem;
i18n?: EggPluginItem;
watcher?: EggPluginItem;
multipart?: EggPluginItem;
security?: EggPluginItem;
development?: EggPluginItem;
logrotator?: EggPluginItem;
schedule?: EggPluginItem;
static?: EggPluginItem;
jsonp?: EggPluginItem;
view?: EggPluginItem;
jwt?: EggPluginItem;
cors?: EggPluginItem;
validate?: EggPluginItem;
mysql?: EggPluginItem;
sequelize?: EggPluginItem;
redis?: EggPluginItem;
routerPlus?: EggPluginItem;
oss?: EggPluginItem;
}
}
\ No newline at end of file
import 'egg';
declare module 'egg' {
interface Application {
jwt: any;
}
}
\ No newline at end of file
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