project init

This commit is contained in:
System Administrator
2020-03-26 22:41:11 +08:00
commit dfff01e6a0
31 changed files with 8278 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
.DS_Store
node_modules/
dist/
npm-debug.log
yarn-error.log
test/unit/coverage
/.idea/
npm-debug.*
+6
View File
@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="TsLint" enabled="true" level="ERROR" enabled_by_default="true" />
</profile>
</component>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
Executable
+58
View File
@@ -0,0 +1,58 @@
# node-loan-center
node服务中心
## 快速入门
详细文档,参见 [egg 文档][eggjs], [midway 文档][midway]。
### 安装 启动
```bash
$ yarn
$ yarn dev
$ open http://localhost:7001/
```
### 部署
```bash
$ npm start
$ npm stop
```
### 单元测试
```
yarn test
```
- 具体参见 [midway 文档 - 单元测试](https://eggjs.org/zh-cn/core/unittest)。
### 目录结构
```
├─dist
├─logs
│ ├─ELKLog //上报kibana日志系统log
| | ├─info.log
| | └─error.log
│ └─node-loan-center // 系统日志
├─node_modules
├─src //开发目录
│ ├─app
│ │ ├─controller
│ │ ├─extend
│ │ ├─helper
│ │ ├─middleware
│ │ └─public // 静态目录
│ ├─config // 配置文件目录,包含testprodlocal等config文件
│ ├─interface // ts接口目录
│ ├─lib // 工具类目录,如基类
│ └─service
└─test // 单元测试用例目录
└─app
└─controller
```
[midway]: https://midwayjs.org
[git-rules]: https://confluence.sui.work/pages/viewpage.action?pageId=51120607
[eggjs]: https://eggjs.org/zh-cn/
Executable
+14
View File
@@ -0,0 +1,14 @@
environment:
matrix:
- nodejs_version: '10'
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
Executable
+55
View File
@@ -0,0 +1,55 @@
{
"name": "game-node-center",
"version": "1.0.0",
"description": "node服务中心",
"private": true,
"dependencies": {
"egg-cors": "^2.2.3",
"egg-redis": "^2.4.0",
"egg-scripts": "^2.10.0",
"jsonwebtoken": "^8.5.1",
"midway": "^1.0.0"
},
"devDependencies": {
"@types/mocha": "^5.2.7",
"@types/node": "^10.5.5",
"cross-env": "^6.0.0",
"egg-ci": "^1.8.0",
"midway-bin": "1",
"midway-mock": "1",
"ts-node": "^8.3.0",
"tslib": "^1.8.1",
"tslint": "^5.11.0",
"tslint-config-egg": "^1.0.0",
"typescript": "^3.5.0"
},
"engines": {
"node": ">=10.16.0"
},
"scripts": {
"start": "egg-scripts start --daemon --title=midway-server-node-loan-center --framework=midway --ts",
"stop": "egg-scripts stop --title=midway-server-node-loan-center",
"start_build": "npm run build && cross-env NODE_ENV=development midway-bin dev",
"clean": "midway-bin clean",
"dev": "cross-env NODE_ENV=local midway-bin dev --ts",
"debug": "cross-env NODE_ENV=local midway-bin debug --ts",
"test": "npm run lint && midway-bin test --ts",
"cov": "midway-bin cov --ts",
"lint": "tslint --fix -p tsconfig.json -t stylish",
"ci": "npm run cov",
"build": "midway-bin build -c"
},
"ci": {
"version": "10"
},
"midway-bin-clean": [
".vscode/.tsbuildinfo",
"dist"
],
"repository": {
"type": "git",
"url": ""
},
"author": "Cai",
"license": "MIT"
}
Executable
+15
View File
@@ -0,0 +1,15 @@
import { Application } from 'egg';
import * as path from 'path';
import ElkTransport from './app/helper/logTransport';
export default (app: Application) => {
app.beforeStart(async () => {});
app.logger.set('remoteInfo', new ElkTransport({
level: 'INFO',
file: path.join(app.baseDir, '../logs/ELKLog/info.log'),
}));
app.logger.set('remoteError', new ElkTransport({
level: 'ERROR',
file: path.join(app.baseDir, '../logs/ELKLog/error.log'),
}));
};
+23
View File
@@ -0,0 +1,23 @@
import BaseController from "../../lib/baseController";
import {controller, inject, post, provide} from "midway";
import {IAccountService} from '../../interface/IAccountService'
import {IAccountInfo} from "../../interface/IAccountInfo";
@provide()
@controller('/node/user/')
export class Account extends BaseController {
@inject('AccountService')
service: IAccountService;
@post('/login')
async login() {
try {
const accountInfo: IAccountInfo = {userAccount: 'cai', password: '123'};
const result = await this.service.login(accountInfo);
this.success(result)
} catch (e) {
this.fail(e)
}
}
}
+34
View File
@@ -0,0 +1,34 @@
import { Context } from 'egg';
const LOG_COLLECTION = Symbol('Context#logCollection');
export default {
context: this,
/**
* 获取收集的日志信息
* @returns {any}
*/
getLogs(this: Context) {
const result: any = {};
for (const [ k, val ] of this.logCollection) {
result[k] = val;
}
return result;
},
/**
* 收集日志信息
* @param {string} key
* @param {string} value
*/
setLogCollection(this: Context, key: string, value: string) {
this.logCollection.set(key, value);
},
get logCollection(this: Context) {
if (!this.context[LOG_COLLECTION]) {
this.context[LOG_COLLECTION] = new Map();
}
return this.context[LOG_COLLECTION];
},
set logCollection(this: Context, value) {
this.context[LOG_COLLECTION] = value;
},
};
+92
View File
@@ -0,0 +1,92 @@
import { LoggerLevel, Context } from 'egg';
import { FileBufferTransport } from 'egg-logger';
import * as iconv from 'iconv-lite';
import * as moment from 'moment';
import * as os from 'os';
import { OSLogField, BusinessLogField } from '../../interface/logInterface';
const { uid, username } = os.userInfo();
/**
* 日志格式化
*/
class LogFormat extends Map {
logField: BusinessLogField;
osField: OSLogField;
constructor() {
super();
this.osField = {
pid: process.pid,
nodeVersion: process.version,
launchTime: moment().format('YYYY-MM-DD HH:mm:ss'),
osUser: username,
osUid: uid,
};
}
/**
* Log日志进行utf-8编码
*/
protected toBuffer() {
const fields = { ...this.osField, ...this.logField };
const str = JSON.stringify(fields) + '\n';
return iconv.encode(str, 'utf8');
}
/**
* 格式化Log上下文
* @param collect 日志对象
* @param level 日志级别
*/
public formatFetchInfoMsg(collect: any, level: LoggerLevel) {
this.logField = {
fetchConsumeTime: 0,
level: '',
message: '',
requestTime: '',
stack: '',
status: '',
timestamp: '',
total: 0,
requestBody: {},
method: '',
url: '',
};
this.logField.timestamp = collect.startTime;
this.logField.requestTime = moment(collect.startTime).format('YYYY-MM-DD HH:mm:ss');
this.logField.status = collect.status;
this.logField.message = collect.message;
this.logField.stack = collect.stack;
this.logField.level = collect.level || level;
this.logField.url = collect.url;
this.logField.total = collect.end - collect.start;
if (collect.fetchStart && collect.fetchEnd) {
this.logField.requestBody = collect.requestBody;
this.logField.method = collect.method;
this.logField.fetchConsumeTime = collect.fetchEnd - collect.fetchStart;
}
return this.toBuffer();
}
}
export default class ElkTransport extends FileBufferTransport {
log(this: Context, level: LoggerLevel, args: any) {
const logFormat = new LogFormat();
const logInfo = args[0];
let buf;
if (!this._stream) {
const err = new Error(`${this.options.file} log stream had been closed`);
console.error(err.stack);
return;
}
if (typeof logInfo === 'string') {
buf = logFormat.formatFetchInfoMsg({ message: logInfo }, level);
} else {
buf = logFormat.formatFetchInfoMsg(logInfo, level);
}
if (buf.length) {
this._write(buf);
}
}
}
+25
View File
@@ -0,0 +1,25 @@
import { Context, EggAppConfig } from 'egg';
/**
* elk 日志拦截中间件
* @description 接口请求拦截,进行日志上报,根据config的环境配置,设置日志收集,prod默认开启
* @param {EggAppConfig["elkLogger"]} options config 配置项 elkLogger
* @returns {any}
*/
export default function elkLogger(options: EggAppConfig['elkLogger']): any {
return async (ctx: Context, next: () => Promise<any>) => {
const { match, enable } = options;
// 是否符合配置规则
if (match(ctx) && enable) {
ctx.setLogCollection('fetchStart', Date.now());
ctx.setLogCollection('url', ctx.url);
ctx.setLogCollection('requestBody', ctx.request.body);
ctx.setLogCollection('message', `${ctx.request.method} ${ctx.url} info`);
ctx.setLogCollection('level', 'INFO');
await next();
ctx.setLogCollection('status', ctx.res.statusCode);
ctx.setLogCollection('fetchEnd', Date.now());
ctx.logger.info(ctx.getLogs());
}
};
}
+22
View File
@@ -0,0 +1,22 @@
import { Context } from 'egg';
/**
* 404状态处理
* @returns {any}
*/
export default function notFound(): any {
return async (ctx: Context, next: () => Promise<any>) => {
await next();
if (ctx.status === 404 && !ctx.body) {
if (ctx.acceptJSON) {
ctx.body = {
code: '404',
data: {},
msg: '页面坐火箭去了',
};
} else {
ctx.body = '<h1>Page Not Found</h1>';
}
}
};
}
+1
View File
@@ -0,0 +1 @@
## public static file directory!
+80
View File
@@ -0,0 +1,80 @@
import { EggAppConfig, EggAppInfo, PowerPartial, Context } from 'midway';
export type DefaultConfig = PowerPartial<EggAppConfig>;
export default (appInfo: EggAppInfo) => {
const config = {} as DefaultConfig;
// use for cookie sign key, should change to your own and keep security
config.keys = appInfo.name + '_{{keys}}';
// elk日志中间件,404处理中间件
config.middleware = [ 'elkLogger', 'notFound' ];
// 合成配置
const bizConfig = {
sourceUrl: '',
elkLogger: {
// 请求url匹配规则
match(ctx: Context) {
const reg = /.*/;
return reg.test(ctx.url);
},
// 是否启用
enable: true,
},
};
// 安全处理
config.security = {
csrf: {
enable: false,
},
methodnoallow: {
enable: false,
},
};
// CORS 跨域处理
config.cors = {
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS',
credentials: true,
origin(ctx: Context) {
const origin: string = ctx.get('origin');
// 允许*.ssjlicai.com域名访问
if (origin.indexOf('172.22.88.118') > -1) {
console.log('come in');
return origin;
} else {
return 'marketres.ssjlicai.com';
}
},
};
// 日志配置
config.logger = {
outputJSON: false,
appLogName: 'app.log',
coreLogName: 'core.log',
agentLogName: 'agent.log',
errorLogName: 'error.log',
};
// 业务接口domain
config.apiDomain = {
loanDomain: 'https://lcts3.ssjlicai.com',
};
// redis
config.redis = {
client: {
port: 6379, // Redis port
host: '127.0.0.1', // Redis host
password: '123456',
db: 0,
},
};
return {
...bizConfig,
...config,
};
};
+12
View File
@@ -0,0 +1,12 @@
export const development = {
watchDirs: [
'app',
'lib',
'service',
'config',
'app.ts',
'agent.ts',
'interface.ts',
],
overrideDefault: true,
};
+25
View File
@@ -0,0 +1,25 @@
import { EggAppConfig, PowerPartial } from 'egg';
import {Context} from "midway";
export default () => {
const config: PowerPartial<EggAppConfig> = {};
// 业务接口domain
config.apiDomain = {
loan: '',
};
// CORS 跨域处理
config.cors = {
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS',
credentials: true,
origin(ctx: Context) {
const origin: string = ctx.get('origin');
// 允许*.ssjlicai.com域名访问
if (origin.indexOf('.ssjlicai.com') > -1) {
return origin;
} else {
return 'marketres.ssjlicai.com';
}
},
};
return config;
};
+10
View File
@@ -0,0 +1,10 @@
import { EggAppConfig, PowerPartial } from 'egg';
export default () => {
const config: PowerPartial<EggAppConfig> = {};
// 业务接口domain
config.apiDomain = {
loan: '',
};
return config;
};
+15
View File
@@ -0,0 +1,15 @@
import { EggPlugin } from 'egg';
const plugin: EggPlugin = {
static: true,
cors: {
enable: true,
package: 'egg-cors',
},
redis: {
enable: true,
package: 'egg-redis',
}
};
export default plugin;
+5
View File
@@ -0,0 +1,5 @@
export interface IAccountInfo {
userAccount: string;
password: string;
}
+7
View File
@@ -0,0 +1,7 @@
import {IAccountInfo} from "./IAccountInfo";
import {ILoginResult} from "./ILoginResult";
export interface IAccountService {
login(accountInfo: IAccountInfo): Promise<ILoginResult>;
authUser(userInfo: IAccountInfo): boolean;
}
+15
View File
@@ -0,0 +1,15 @@
import { HttpMethod } from 'urllib';
/**
* @description User-Service abstractions
*/
export interface IFetchOptions {
method?: string;
url: string;
body: object;
head?: object;
timeout?: number;
headers?: object;
type?: HttpMethod;
ssjToken: string;
}
+3
View File
@@ -0,0 +1,3 @@
export interface ILoginResult {
token:string
}
+15
View File
@@ -0,0 +1,15 @@
export enum ProductType {
/**
* 网贷(二部产品)
*/
WEB_LOAN,
/**
* 信用卡贷款(一部产品)
*/
CREDIT_CARD,
}
export interface IRequestBody {
head: any;
body: any;
}
+11
View File
@@ -0,0 +1,11 @@
export enum ResultCode {
SUCCESS = '000000',
FAILD = '100000',
UNAUTH = '999999'
}
export interface IResult {
code: ResultCode;
data: any;
message: string;
}
+45
View File
@@ -0,0 +1,45 @@
import { inject, Context } from 'midway';
import { IRequestBody } from '../interface/IRequestBody';
import {IResult, ResultCode} from "../interface/IResult";
export default class BaseController {
@inject()
protected ctx: Context;
/**
* 获取请求内容
* @returns {IRequestBody}
*/
public getRequestBody(): IRequestBody {
let params: IRequestBody;
params = this.ctx.request.body.params && JSON.parse(this.ctx.request.body.params) || {};
console.log(this.ctx.request.body, 'params');
return params;
}
/**
* 失败回调封装
* @param {Object} data
*/
public success(data: any) {
const result: IResult = {
code: ResultCode.SUCCESS,
data: data,
message: ''
}
this.ctx.body = result
}
/**
* 错误回调封装
* @param {string} message
*/
public fail(message: string) {
const result: IResult = {
code: ResultCode.FAILD,
data: {},
message
};
this.ctx.body = result
}
}
+46
View File
@@ -0,0 +1,46 @@
import { Context } from 'egg';
import { IFetchOptions } from '../interface/IFetchOptions';
import { inject, config } from 'midway';
export default class BaseService {
@inject()
protected ctx: Context;
@config('apiDomain')
protected config: any;
/**
* 处理请求
* @param {IFetchOptions} option
* @returns {Promise<any>}
*/
public async fetch(option: IFetchOptions) {
try {
const data = {
head: { ...option.head },
body: { ...option.body },
};
// 用户登录标识
const headers = {
Authorization: `Bearer ${option.ssjToken}`,
};
const ajaxUrl = this.config.loanDomain + option.url;
// 发起服务请求
const result = await this.ctx.curl(ajaxUrl, {
data,
type: option.type || 'POST',
headers,
dataType: 'json',
});
// 接口请求失败日志上报
if (result.status !== 200) {
this.ctx.logger.error(this.ctx.getLogs());
}
return result.data;
} catch (e) {
this.ctx.logger.error(this.ctx.getLogs());
}
}
}
+68
View File
@@ -0,0 +1,68 @@
import BaseService from '../lib/baseService';
import {Context, inject, provide, Application} from "midway";
import {IAccountInfo} from "../interface/IAccountInfo";
import {IAccountService} from "../interface/IAccountService";
import { sign } from 'jsonwebtoken';
import {ILoginResult} from "../interface/ILoginResult";
@provide('AccountService')
export class AccountService extends BaseService implements IAccountService{
@inject()
app: Application;
@inject()
ctx: Context;
salt: 'test123';
public login(accountInfo: IAccountInfo): Promise<ILoginResult> {
return new Promise( async (resolve,reject) => {
try {
// 校验是否登录
let token = await this.isLogin(accountInfo);
if(!token) {
// 校验用户信息
const isAuth = await this.authUser(accountInfo)
if(isAuth){
token = await this.setToken(accountInfo)
} else {
token = ''
}
}
const result:ILoginResult = { token }
resolve(result)
} catch (e) {
reject('auth error');
}
})
}
private async isLogin(accountInfo: IAccountInfo): Promise<string> {
try {
const token: string = await this.getToken(accountInfo.userAccount)
return token
} catch (e) {
throw 'isLogin error'
}
}
public async authUser(accountInfo: IAccountInfo) {
return accountInfo.userAccount === 'cai' && accountInfo.password === '123'
}
private async setToken(accountInfo: IAccountInfo) {
try {
let token = sign({userName: accountInfo.userAccount}, this.salt);
await this.app.redis.set(accountInfo.userAccount, token)
return token
} catch (e) {
throw `redis error: ${e.msg}`
}
}
private getToken(userAccount: string) {
let token = this.app.redis.get(userAccount);
console.log('token', token)
return token
}
}
+50
View File
@@ -0,0 +1,50 @@
/* tslint:disable */
const { app, assert } = require('midway-mock/bootstrap');
/* tslint:enable */
describe('test/app/controller/home.test.ts', () => {
it('should assert', async () => {
const pkg = require('../../../package.json');
assert(app.config.keys.startsWith(pkg.name));
// const ctx = app.mockContext({});
// await ctx.service.xx();
});
it('should POST /ocrAuth', () => {
/* tslint:disable */
return app.httpRequest().
post('/node/ocrAuth').
set('Authorization', 'Bearer ad7bc5ae-e19e-4d39-9a93-aa75bc01ede1').
send({
'params': JSON.stringify({
'head': {
'clientId': 0,
'appUDID': '',
'appVersion': '',
'channelId': '',
'innerMedia': '',
'outerMedia': '',
'subClientId': '0',
'systemVersion': 'undefined',
'origin': '',
},
'body': {
'name': '蔡建帮',
'address': '测试',
'authOrg': '测试政府',
'gender': '男',
'idNo': '445222198911111111',
'nation': '中国',
'validDate': '20190909 - 20990907',
'frontImage': '',
'backImage': '',
'productId': 'L0003',
'channelType': 1,
},
}),
}).
expect(200);
});
});
Executable
+29
View File
@@ -0,0 +1,29 @@
{
"compileOnSave": true,
"compilerOptions": {
"declaration": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"inlineSourceMap": true,
"module": "commonjs",
"newLine": "lf",
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"outDir": "dist",
"pretty": true,
"skipLibCheck": true,
"strict": true,
"strictPropertyInitialization": false,
"stripInternal": true,
"target": "ES2018"
},
"exclude": [
"app/public",
"app/views",
"dist",
"node_modules*",
"test",
"**/*.d.ts",
"**/*.spec.ts"
]
}
Executable
+6
View File
@@ -0,0 +1,6 @@
{
"extends": ["tslint-config-egg"],
"rules": {
"linebreak-style": [ true, "CRLF" ]
}
}
Executable
+7477
View File
File diff suppressed because it is too large Load Diff