Merge branch 'dev' into 'master'

Dev to master

See merge request !15
This commit is contained in:
苏文雄 2017-07-27 11:57:29 +08:00
commit ca4c5b75d2
63 changed files with 1039 additions and 813 deletions

42
.eslintrc.js Normal file
View File

@ -0,0 +1,42 @@
module.exports = {
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"node": true
},
"extends": "eslint:recommended",
"parser": "babel-eslint",
"parserOptions": {
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
},
"sourceType": "module"
},
"plugins": [
"react"
],
"rules": {
"indent": [
"error",
4,
{
"SwitchCase": 1
}
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
],
"semi": [
"error",
"always"
],
"strict": 0
}
};

View File

@ -1,22 +1,22 @@
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { Route, HashRouter, Redirect, Switch } from 'react-router-dom'
import { Home, ProjectGroups, Interface, News, AddInterface } from './containers/index'
import User from './containers/User/User.js'
import Header from './components/Header/Header'
import Footer from './components/Footer/Footer'
import Loading from './components/Loading/Loading'
import { checkLoginState } from './actions/login'
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Route, HashRouter, Redirect, Switch } from 'react-router-dom';
import { Home, ProjectGroups, Interface, News, AddInterface } from './containers/index';
import User from './containers/User/User.js';
import Header from './components/Header/Header';
import Footer from './components/Footer/Footer';
import Loading from './components/Loading/Loading';
import { checkLoginState } from './actions/login';
import { requireAuthentication } from './components/AuthenticatedComponent';
const LOADING_STATUS = 0;
@connect(
state => {
return{
loginState:state.login.loginState
}
return {
loginState: state.login.loginState
};
},
{
checkLoginState
@ -27,20 +27,22 @@ export default class App extends Component {
super(props);
this.state = {
login: LOADING_STATUS
}
};
}
static propTypes = {
checkLoginState:PropTypes.func,
loginState:PropTypes.number
}
checkLoginState: PropTypes.func,
loginState: PropTypes.number
};
componentDidMount() {
this.props.checkLoginState();
}
route = (status) => {
let r;
if (status === LOADING_STATUS) {
return <Loading visible/>
return <Loading visible />;
} else {
r = (
<HashRouter>
@ -50,20 +52,21 @@ export default class App extends Component {
<Route path="/" component={Home} exact />
<Switch>
<Redirect exact from='/group' to='/group/1' />
<Route exact path="/group/:groupName" component={ requireAuthentication(ProjectGroups) } />
<Route exact path="/group/:groupName" component={requireAuthentication(ProjectGroups)} />
</Switch>
<Route path="/Interface" component={requireAuthentication(Interface)} />
<Route path="/user" component={requireAuthentication(User)} />
<Route path="/News" component={requireAuthentication(News)} />
<Route path="/AddInterface" component={ requireAuthentication(AddInterface) } />
<Route path="/AddInterface" component={requireAuthentication(AddInterface)} />
</div>
<Footer/>
<Footer />
</div>
</HashRouter>
)
}
return r
return r;
}
render() {
return this.route(this.props.loginState);
}

View File

@ -3,9 +3,18 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types'
import { changeMenuItem } from '../actions/menu'
@connect(
(state) => {
return{
isAuthenticated: state.login.isLogin
}
},
{
changeMenuItem
}
)
export function requireAuthentication(Component) {
class AuthenticatedComponent extends React.Component {
return class AuthenticatedComponent extends React.Component {
constructor(props){
super(props);
}
@ -37,19 +46,8 @@ export function requireAuthentication(Component) {
}
</div>
)
}
}
return connect(
(state) => {
return{
isAuthenticated: state.login.isLogin
}
},
{
changeMenuItem
}
)(AuthenticatedComponent);
}

View File

@ -43,9 +43,9 @@ class FootItem extends Component {
render () {
return (
<div className = 'footItem'>
<h4><Icon type= { this.props.iconType } style={{ fontSize: 16 }} /> { this.props.title } </h4>
<h4><Icon type= { this.props.iconType } style={{ fontSize: 16 }} />&nbsp;&nbsp; { this.props.title } </h4>
{ this.props.linkList.map(function(item,i){
return (<div key = {i}><a href = { item.itemLink }><span>{ item.itemTitle }</span></a></div>);
return (<div key = {i}>&nbsp;&nbsp;<a href = { item.itemLink }><span>{ item.itemTitle }</span></a></div>);
}) }
</div>
);

View File

@ -1,4 +1,5 @@
@import '../../styles/common.scss';
@import '../../styles/mixin.scss';
.footer{
// max-width: 12rem;
@ -31,7 +32,7 @@
z-index: 1;
}
.footContent{
max-width: 11rem;
@include row-width-limit;
width:90%;
margin:0px auto;
overflow: hidden;
@ -47,12 +48,12 @@
}
a{
font-weight: 200;
color: #108ee9;
color: #b3bdc1;
&:hover{
color: white;
}
}
}
.copyRight{
padding: 24px 2%;
@ -68,4 +69,4 @@
position: relative;
text-indent: 0em;
}
}
}

View File

@ -66,8 +66,26 @@ ToolUser.propTypes={
};
@connect(
(state) => {
return{
user: state.login.userName,
uid: state.login.uid,
msg: null,
login:state.login.isLogin,
curKey: state.menu.curKey
}
},
{
loginTypeAction,
logoutActions,
checkLoginState,
changeMenuItem
}
)
@withRouter
class HeaderCom extends Component {
export default class HeaderCom extends Component {
constructor(props) {
super(props);
}
@ -128,15 +146,16 @@ class HeaderCom extends Component {
}
render () {
const { login, user, msg, uid, curKey } = this.props;
const headerStyle = {
'background': 'url(./image/bg-img.jpg) no-repeat center',
'backgroundSize':'cover'
}
const headerImgStyle = login?{}:{
'background': 'url(./image/header-bg-img.jpg) no-repeat',
'backgroundSize':'100% 100%'
};
const headerShadeStyle = login?{}:{
'background': 'linear-gradient(to bottom,rgba(0,0,0,0.6),rgba(0,0,0,0.5))'
};
return (
<acticle className={`header-box`} style={headerStyle}>
<Header style={{
background: "linear-gradient(to bottom,rgba(64,64,64,1),rgba(64,64,64,0.9))"
}}>
<acticle className={`header-box`} style={headerImgStyle}>
<Header style={headerShadeStyle}>
<div className="content">
<div className="logo">
<Link to="/" onClick={this.relieveLink}>YAPI</Link>
@ -173,22 +192,4 @@ class HeaderCom extends Component {
</acticle>
)
}
}
export default connect(
(state) => {
return{
user: state.login.userName,
uid: state.login.uid,
msg: null,
login:state.login.isLogin,
curKey: state.menu.curKey
}
},
{
loginTypeAction,
logoutActions,
checkLoginState,
changeMenuItem
}
)(HeaderCom)
}

View File

@ -1,26 +1,19 @@
@import '../../styles/common.scss';
@import '../../styles/mixin.scss';
$color-white : #fff;
$color-blue : #108ee9;
$color-blue-deeper: #34495E;
$color-grey-deep : #929aac;
$color-black-light : #404040;
/* .header-box.css */
//.light{
// background-color: #f7fafc;
// color: $color-blue;
//}
//.dark {
// background-color: $color-black-light;
// color: $color-white;
//}
.header-box {
display: block;
font-size: 0.14rem;
z-index: 99;
// 内容宽度
.content {
max-width: 11rem;
@include row-width-limit;
margin: 0 auto;
zoom: 1;
overflow: hidden;
@ -85,4 +78,3 @@ $color-black-light : #404040;
}
}
}

View File

@ -1,7 +1,3 @@
export default {
PAGE_LIMIT: 10, // 默认每页展示10条数据
// layout
ROW_MIN_WIDTH: '9.7rem', // 适应小屏幕分辨率
ROW_MAX_WIDTH: '11.7rem' // 适应大屏幕分辨率
PAGE_LIMIT: 10 // 默认每页展示10条数据
}

View File

@ -254,9 +254,6 @@ class AddInterface extends Component {
<Result isSave={isSave} mockJson={mockJson} />
<MockUrl mockURL={mockURL} />
</TabPane>
{
// <TabPane tab="Mock" key="2">mock</TabPane>
}
<TabPane tab="请求接口" key="3">
<InterfaceTest />
</TabPane>

View File

@ -1,3 +1,5 @@
@import '../../styles/mixin.scss';
.add-interface-box {
-webkit-box-flex: 1;
font-size: .14rem;
@ -5,7 +7,7 @@
overflow-y: auto;
.content {
max-width: 11rem;
@include row-width-limit;
margin: 24px auto;
border-radius: 4px;
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.20);
@ -169,7 +171,7 @@
color: #CCC;
}
}
/* .mock-url-box.css */
.mock-url-box {
clear: both;

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Button, Input } from 'antd'
import { Button, Input, Select, Card, Alert } from 'antd'
import { autobind } from 'core-decorators';
import crossRequest from 'cross-request';
import { withRouter } from 'react-router';
@ -13,6 +13,8 @@ import {
import './InterfaceTest.scss'
const { TextArea } = Input;
const InputGroup = Input.Group;
const Option = Select.Option;
@connect(
state => ({
@ -40,8 +42,16 @@ export default class InterfaceTest extends Component {
}
state = {
res: {},
header: {}
res: '',
method: 'GET',
domains: [],
pathname: '',
query: {},
params: {},
paramsNotJson: false,
headers: {},
search: '',
currDomain: ''
}
constructor(props) {
@ -49,32 +59,72 @@ export default class InterfaceTest extends Component {
}
componentWillMount() {
this.interfacePropsToState()
}
componentWillReceiveProps(nextProps) {
this.interfacePropsToState(nextProps)
}
@autobind
interfacePropsToState(nextProps) {
const props = nextProps || this.props;
const { method, url, seqGroup, interfaceProject } = props;
const { prd_host, basepath, protocol, env } = interfaceProject;
const pathname = (basepath + url).replace(/\/+/g, '/');
const domains = {prd: protocol + '://' + prd_host};
env.forEach(item => {
domains[item.name] = item.domain;
})
const query = {};
let params = {};
let reqParams = this.props.reqParams ? this.props.reqParams : '{}';
let paramsNotJson = false;
try {
reqParams = JSON.parse(reqParams)
paramsNotJson = false;
} catch (e) {
paramsNotJson = true;
}
if (method === 'GET') {
Object.keys(reqParams).forEach(key => {
const value = typeof reqParams[key] === 'object' ? JSON.stringify(reqParams[key]) : reqParams[key].toString();
query[key] = value;
})
} else if (method === 'POST') {
params = reqParams;
}
const headers = {}
seqGroup.forEach((headerItem) => {
if (headerItem.name) {
headers[headerItem.name] = headerItem.value;
}
})
this.setState({
domains,
pathname,
query,
params,
paramsNotJson,
headers,
currDomain: domains.prd
});
}
@autobind
testInterface() {
const { method, url, seqGroup, interfaceProject } = this.props;
const { prd_host, basepath, protocol } = interfaceProject;
const reqParams = JSON.parse(this.props.reqParams);
const headers = {}
let query = {};
if (method === 'GET') {
Object.keys(reqParams).forEach(key => {
const value = typeof reqParams[key] === 'object' ? JSON.stringify(reqParams) : reqParams.toString();
query[key] = value;
})
}
seqGroup.forEach((headerItem) => {
headers[headerItem.name] = headerItem.value;
})
const { method } = this.props;
const { pathname, query, headers, params, currDomain } = this.state;
const urlObj = URL.parse(currDomain);
const href = URL.format({
protocol: protocol || 'http',
host: prd_host,
pathname: (basepath + url).replace(/\/+/g, '/'),
protocol: urlObj.protocol || 'http',
host: urlObj.host,
pathname,
query
});
@ -82,9 +132,7 @@ export default class InterfaceTest extends Component {
url: href,
method,
headers,
data: {
a:1
},
data: params,
success: (res, header) => {
console.log(header)
this.setState({res})
@ -92,71 +140,143 @@ export default class InterfaceTest extends Component {
})
}
@autobind
changeDomain(value) {
const domain = this.state.domains[value];
this.setState({ currDomain: domain });
}
@autobind
changeHeader(e, key) {
const headers = JSON.parse(JSON.stringify(this.state.headers));
headers[key] = e.target.value;
this.setState({ headers });
}
@autobind
changeQuery(e, key) {
const query = JSON.parse(JSON.stringify(this.state.query));
query[key] = e.target.value;
this.setState({ query });
}
@autobind
changeParams(e, key) {
const params = JSON.parse(JSON.stringify(this.state.params));
params[key] = e.target.value;
this.setState({ params });
}
hasCrossRequestPlugin() {
const dom = document.getElementById('y-request');
return dom.getAttribute('key') === 'yapi';
}
render () {
const { method, url, seqGroup, interfaceName, interfaceProject } = this.props;
const { prd_host, basepath, protocol } = interfaceProject;
const reqParams = JSON.parse(this.props.reqParams);
let query = {};
if (method === 'GET') {
Object.keys(reqParams).forEach(key => {
const value = typeof reqParams[key] === 'object' ? JSON.stringify(reqParams[key]) : reqParams[key].toString();
query[key] = value;
})
}
const href = URL.format({
protocol: protocol || 'http',
host: prd_host,
pathname: (basepath + url).replace(/\/+/g, '/'),
const { interfaceName, method } = this.props;
const { domains, pathname, query, headers, params, paramsNotJson } = this.state;
const search = URL.format({
query
});
const hasPlugin = this.hasCrossRequestPlugin();
return (
<div className="interface-test">
<div style={{padding: '0 20%'}}>
{ hasPlugin ? '' :
<Alert
message={
<div>
温馨提示当前正在使用接口测试服务请安装我们为您免费提供的&nbsp;
<a
target="blank"
href="https://chrome.google.com/webstore/detail/cross-request/cmnlfmgbjmaciiopcgodlhpiklaghbok?hl=en-US"
>
测试增强插件 [点击获取]
</a>
</div>
}
type="warning"
/>
}
</div>
<div className="interface-name">{interfaceName}</div>
<div className="req-part">
<div className="req-row method">
METHOD<Input value={method} disabled style={{display: 'inline-block', width: 200}} />
</div>
<div className="req-row url">
URL<Input value={href} style={{display: 'inline-block', width: 800, margin: 10}} />
<Button onClick={this.testInterface} type="primary">发送</Button>
</div>
<div className="req-row headers">
HEADERS
{
seqGroup.map((headerItem, index) => {
return (
<div key={index}>
<Input disabled value={headerItem.name} style={{display: 'inline-block', width: 200, margin: 10}} />{' = '}
<Input value={headerItem.value} style={{display: 'inline-block', width: 200, margin: 10}} />
</div>
)
})
}
</div>
<div className="req-row params">
请求参数
{
Object.keys(reqParams).map((key, index) => {
const value = typeof reqParams[key] === 'object' ? JSON.stringify(reqParams[key]) : reqParams[key].toString();
return (
<div key={index}>
<Input disabled value={key} style={{display: 'inline-block', width: 200, margin: 10}} />{' = '}
<Input value={value} style={{display: 'inline-block', width: 200, margin: 10}} />
</div>
)
})
}
<div className="req-row href">
<InputGroup compact style={{display: 'inline-block', width: 680}}>
<Input value={method} disabled style={{display: 'inline-block', width: 80}} />
<Select defaultValue="prd" style={{display: 'inline-block', width: 300}} onChange={this.changeDomain}>
{
Object.keys(domains).map((key, index) => (<Option value={key} key={index}>{domains[key]}</Option>))
}
</Select>
<Input value={pathname+search} disabled style={{display: 'inline-block', width: 300}} />
</InputGroup>
<Button onClick={this.testInterface} type="primary" style={{marginLeft: 10}}>发送</Button>
</div>
<Card title="HEADERS" noHovering style={{marginTop: 10}} className={Object.keys(headers).length ? '' : 'hidden'}>
<div className="req-row headers">
{
Object.keys(headers).map((key, index) => {
return (
<div key={index}>
<Input disabled value={key} style={{display: 'inline-block', width: 200, margin: 10}} />{' = '}
<Input value={headers[key]} onChange={e => this.changeHeader(e, key)} style={{display: 'inline-block', width: 200, margin: 10}} />
</div>
)
})
}
</div>
</Card>
<Card title="Query" noHovering style={{marginTop: 10}} className={Object.keys(query).length ? '' : 'hidden'}>
<div className="req-row query">
{
Object.keys(query).map((key, index) => {
const value = typeof query[key] === 'object' ? JSON.stringify(query[key]) : query[key].toString();
return (
<div key={index}>
<Input disabled value={key} style={{display: 'inline-block', width: 200, margin: 10}} />{' = '}
<Input value={value} onChange={e => this.changeQuery(e, key)} style={{display: 'inline-block', width: 200, margin: 10}} />
</div>
)
})
}
</div>
</Card>
<Card title="Body" noHovering style={{marginTop: 10}} className={Object.keys(params).length ? '' : 'hidden'}>
<div className="req-row params">
{ paramsNotJson ?
<TextArea
value={params}
style={{margin: 10}}
autosize={{ minRows: 2, maxRows: 6 }}
></TextArea> :
Object.keys(params).map((key, index) => {
const value = typeof params[key] === 'object' ? JSON.stringify(params[key]) : params[key].toString();
return (
<div key={index}>
<Input disabled value={key} style={{display: 'inline-block', width: 200, margin: 10}} />{' = '}
<Input value={value} onChange={e => this.changeParams(e, key)} style={{display: 'inline-block', width: 200, margin: 10}} />
</div>
)
})
}
</div>
</Card>
</div>
<div className="res-part">
返回结果
<TextArea value={JSON.stringify(this.state.res, 2)}></TextArea>
</div>
<Card title="返回结果" noHovering style={{marginTop: 10}}>
<div className="res-part">
<div>
<TextArea
value={this.state.res ? JSON.stringify(this.state.res, 2) : ''}
style={{margin: 10}}
autosize={{ minRows: 2, maxRows: 6 }}
></TextArea>
</div>
</div>
</Card>
</div>
)
}

View File

@ -24,7 +24,7 @@ class MockUrl extends Component {
clipboard () {
const btn = document.querySelector('#mock-clipboard')
const txt = document.querySelector('#mock-p').innerHTML
console.log('txt', txt)
new Clipboard(btn, {
text: () => txt,
target () {

View File

@ -6,7 +6,8 @@ import { autobind } from 'core-decorators'
import {
reqTagValue,
reqHeaderValue,
deleteReqHeader
deleteReqHeader,
addReqHeader
} from '../../../actions/addInterface.js'
@connect(
@ -20,7 +21,8 @@ import {
{
reqTagValue,
reqHeaderValue,
deleteReqHeader
deleteReqHeader,
addReqHeader
}
)
@ -30,6 +32,7 @@ class ReqList extends Component {
reqTagValue: PropTypes.func,
reqHeaderValue: PropTypes.func,
deleteReqHeader: PropTypes.func,
addReqHeader: PropTypes.func,
_id: PropTypes.number,
dataNum: PropTypes.number,
value: PropTypes.object
@ -43,12 +46,32 @@ class ReqList extends Component {
handleChange (value) {
const dir = 'AddInterface/edit'
const url = location.href
const newObject = []
if (url.includes(dir)) {
const { seqGroup, value: { id } } = this.props
seqGroup[id].name = value
seqGroup.forEach(v => {
if (id == v.id) {
v.name = value
}
})
seqGroup.forEach(v => {
const {id, name, value} = v
newObject.push({id, name, value})
})
this.props.addReqHeader( newObject )
} else {
const { seqGroup, dataNum } = this.props
seqGroup[dataNum].name = value
seqGroup.forEach(v => {
if (dataNum == v.id) {
v.name = value
}
})
seqGroup.forEach(v => {
const {id, name, value} = v
newObject.push({id, name, value})
})
this.props.addReqHeader(newObject)
}
}
@ -56,7 +79,17 @@ class ReqList extends Component {
handleBlur (e) {
const value = e.target.value
const { seqGroup, value: { id } } = this.props
seqGroup[id].value = value
const newObject = []
seqGroup.forEach(v => {
if (id == v.id) {
v.value = value
}
})
seqGroup.forEach(v => {
const {id, name, value} = v
newObject.push({id, name, value})
})
this.props.addReqHeader(newObject)
}
@autobind
@ -76,13 +109,13 @@ class ReqList extends Component {
render () {
const propsValue = this.props.value
const Option = Select.Option
const value = propsValue.value
const value = propsValue.value || ''
const name = propsValue.name || ''
console.log(name)
return (
<li>
<em className="title">头部标签</em>
<Select defaultValue={name} style={{ width: 220 }} onChange={this.handleChange} size="large">
<Select value={name} style={{ width: 220 }} onChange={this.handleChange} size="large">
<Option value="">选择请求头</Option>
<Option value="Accept">Accept</Option>
<Option value="Accept-Charset">Accept-Charset</Option>
@ -91,7 +124,7 @@ class ReqList extends Component {
<Option value="Accept-Ranges">Accept-Ranges</Option>
</Select>
<em className="title">头部内容</em>
<Input defaultValue={value} placeholder="Basic usage" className="req-content" size="large" onBlur={this.handleBlur} />
<Input value={value} placeholder="Basic usage" className="req-content" size="large" onInput={this.handleBlur} />
<Icon className="dynamic-delete-button" type="minus-circle-o" onClick={this.deleteReqHeader} />
</li>
)

View File

@ -58,6 +58,7 @@ class ReqMethod extends Component {
render () {
const { Option } = Select
const { url, interfaceName, method } = this.props
return (
<table>
<tbody>
@ -65,7 +66,7 @@ class ReqMethod extends Component {
<th>协议 :</th>
<td>
<span className="h3">请求方式</span>
<Select defaultValue={method} style={{ width: 220 }} onChange={this.handleChange} size="large">
<Select value={method} style={{ width: 220 }} onChange={this.handleChange} size="large">
<Option value="POST">POST</Option>
<Option value="GET">GET</Option>
<Option value="PUT">PUT</Option>

View File

@ -27,7 +27,7 @@ class Result extends Component {
render () {
const TabPane = Tabs.TabPane
const { mockJson } = this.props
console.log('mockJson', typeof mockJson, mockJson)
return (
<div className="result">
<Tabs defaultActiveKey="1">

View File

@ -17,19 +17,19 @@ const imgAnim = { y: '+=50', opacity: 0, type: 'from', ease: 'easeOutQuad', dura
const style = {
'height':'100%',
'width':'100%',
'background': 'url(./image/bg-img.jpg) no-repeat center',
'backgroundSize':'cover'
'background': 'url(./image/bg-img.jpg) no-repeat',
'backgroundSize':'100% 100%'
}
const HomeGuest = (props) => (
<div>
<div className="main-one" style = {style}>
<div style={{ background: "linear-gradient(to bottom,rgba(64,64,64,0.9),rgba(64,64,64,0.5))"}}>
<div style={{ background: "linear-gradient(to bottom,rgba(0,0,0,0.5),rgba(0,0,0,0.2))"}}>
<div className="container">
<Row>
<Col span={24}>
<div className="home-des">
<p className="title">YAPI</p>
<div className="detail">一个高效易用可部署的Api管理系统</div>
<div className="detail">高效易用可部署的API管理平台旨在为开发产品测试人员提供更优雅的接口管理服务</div>
</div>
</Col>
</Row>
@ -62,7 +62,7 @@ const HomeGuest = (props) => (
<TweenOne
key="feat-motion-one"
animation={oneAnim}
component="h3"
component="p"
>
<span>特性</span>
</TweenOne>

View File

@ -1,4 +1,5 @@
@import '../../styles/common.scss';
@import '../../styles/mixin.scss';
$color-white : #fff;
$color-blue-lighter : #f1f5ff;
@ -12,7 +13,7 @@ $color-black-lighter: #404040;
-webkit-box-orient: vertical;
.main-one{
.home-des{
color: $color-white;
color: $color-blue-grey-lighter;
padding: .5rem 0 0;
.title{
font-size: .6rem;
@ -29,7 +30,8 @@ $color-black-lighter: #404040;
img{
width: 100%;
height: 100%;
box-shadow : 0 0 3px 0 $color-black-lighter;
//box-shadow : 0 0 3px 0 $color-black-lighter;
box-shadow : 0 30px 60px rgba(0,0,0,0.2);
border-radius: .03rem;
}
}
@ -39,16 +41,17 @@ $color-black-lighter: #404040;
}
.main-one-right{
padding-left: .5rem;
padding-top: .3rem;
}
}
.user-home{
display: flex;
align-items: center;
height: 100%;
max-width: 11rem;
@include row-width-limit;
margin: 1rem auto 0;
.user-des{
max-width: 11rem;
@include row-width-limit;
margin: 0 auto .5rem;
text-align: center;
.title{
@ -82,13 +85,13 @@ $color-black-lighter: #404040;
.feat-part{
padding: .9rem .5rem;
background-color: $color-white;
h3{
p{
display: flex;
height: .3rem;
align-items: center;
padding: 0 .1rem;
margin-bottom: .2rem;
color: #333;
//color: #333;
&:before, &:after{
content: "";
display: inline-block;
@ -104,7 +107,7 @@ $color-black-lighter: #404040;
}
}
.container{
max-width: 11rem;
@include row-width-limit;
margin: 0 auto;
height:100%;
position: relative;
@ -129,7 +132,6 @@ $color-black-lighter: #404040;
.feat-title {
font-size: .16rem;
line-height: .3rem;
color: #333;
}
&:first-child {
.feat-img {
@ -153,5 +155,3 @@ $color-black-lighter: #404040;
}
}
}

View File

@ -58,7 +58,7 @@ class Interface extends Component {
.then(result => {
result = result.data.data
result.map(value => {
value.add_time = moment(value.add_time).format('YYYY-MM-DD HH:mm:ss')
value.add_time = moment(value.add_time*1000).format('YYYY-MM-DD HH:mm:ss')
return value
})
this.props.fetchInterfaceData(result)

View File

@ -1,6 +1,8 @@
@import '../../styles/mixin.scss';
/* .interface-box.css */
.interface-box {
max-width: 11rem;
@include row-width-limit;
margin: 24px auto;
border-radius: 4px;
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.20);

View File

@ -65,10 +65,14 @@ class InterfaceTable extends Component {
title: '接口名称',
dataIndex: 'title',
key: 'title'
}, {
},{
title: '接口URL',
dataIndex: 'path',
key: 'path'
},{
title: '请求方式',
dataIndex: 'method',
key: 'method'
},{
title: '更新日期',
dataIndex: 'add_time',
@ -77,14 +81,11 @@ class InterfaceTable extends Component {
title: '功能',
'key': 'action',
render: (data) => {
// const deleteInterface = this.deleteInterface.bind(this, data._id)
const confirm = this.confirm.bind(this, data._id)
return (
<span>
<Link to={`/AddInterface/edit/${data._id}`}><span>编辑</span></Link>
<span className="ant-divider" />
<Link to={`/AddInterface/edit/${data._id}`}><span>测试</span></Link>
<span className="ant-divider" />
<Popconfirm title="是否删除接口!" onConfirm={confirm} okText="Yes" cancelText="No">
<a href="">删除</a>
</Popconfirm>

View File

@ -18,14 +18,14 @@
.qsso-breakline{
display: flex;
align-items: center;
color: #999;
color: #f7fafc;
margin: .2rem auto;
&:before, &:after{
content: "";
display: inline-block;
height: .02rem;
flex: 1;
border-top: .01rem solid #bbb;
border-top: .01rem solid #f7fafc;
}
.qsso-breakword{
padding: 0 .1rem;

View File

@ -7,8 +7,12 @@ import RegForm from './Reg';
import './Login.scss';
const TabPane = Tabs.TabPane;
class LoginWrap extends Component {
@connect(
state =>({
loginWrapActiveKey: state.login.loginWrapActiveKey
})
)
export default class LoginWrap extends Component {
constructor(props){
super(props);
}
@ -32,9 +36,3 @@ class LoginWrap extends Component {
);
}
}
export default connect(
state =>({
loginWrapActiveKey: state.login.loginWrapActiveKey
})
)(LoginWrap)

View File

@ -1,6 +1,8 @@
@import '../../styles/mixin.scss';
/* .interface-box.css */
.news-box {
max-width: 11rem;
@include row-width-limit;
display: -webkit-box;
-webkit-box-flex: 1;
margin: .88rem auto 0 auto;

View File

@ -37,14 +37,15 @@
}
.group-operate {
height: 48px;
min-width: 263px;
padding: 10px 6px;
background: #eee;
display: flex;
justify-content: space-around;
.search {
display: inline-block;
margin-right: 6px;
width: 162px;
flex-grow: 1;
}
}
.group-list {
max-height: 650px;

View File

@ -1,4 +1,6 @@
@import '../../styles/mixin.scss';
.g-doc {
max-width: 11rem;
@include row-width-limit;
margin: .24rem auto;
}

View File

@ -206,13 +206,18 @@ class ProjectList extends Component {
componentWillReceiveProps(nextProps) {
// 切换分组
if (this.props.currGroup !== nextProps.currGroup) {
this.props.fetchProjectList(nextProps.currGroup._id, this.props.currPage).then((res) => {
if (res.payload.data.errcode) {
message.error(res.payload.data.errmsg);
} else {
this.props.changeTableLoading(false);
}
});
if (nextProps.currGroup._id) {
this.props.fetchProjectList(nextProps.currGroup._id, this.props.currPage).then((res) => {
if (res.payload.data.errcode) {
message.error(res.payload.data.errmsg);
} else {
this.props.changeTableLoading(false);
}
});
} else {
// 无分组的时候停止loading状态
this.props.changeTableLoading(false);
}
}
// 切换项目列表
@ -306,7 +311,9 @@ class ProjectList extends Component {
</Form>
</Modal>
<UpDateModal/>
<Button className="m-btn" icon="plus" type="primary" onClick={this.showAddProjectModal}>创建项目</Button>
<Button className="m-btn" icon="plus" type="primary"
onClick={this.showAddProjectModal}
disabled={this.props.currGroup._id ? false : true}>创建项目</Button>
<Table
className="m-table"
bordered={true}

View File

@ -1,16 +1,18 @@
@import '../../styles/mixin.scss';
/* .user-box.css */
.user-box {
max-width: 11rem;
@include row-width-limit;
display: -webkit-box;
-webkit-box-flex: 1;
margin: .88rem auto 0 auto;
// font-size: 0.14rem;
margin-top: 40px;
margin-bottom: 55px;
.user-list {
width: 216px;
@ -19,7 +21,7 @@
border-radius:5px;
margin-top: 15px;
.search{
margin: 5px;
margin: 5px;
}
ul{border:none}
}
@ -38,7 +40,7 @@
.user-table {
-webkit-box-flex: 1;
padding: 24px;
margin-left: 15px;
margin-left: 15px;
border-radius:5px;
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.20);
background: #FFF;
@ -96,4 +98,4 @@
}
}
}

View File

@ -48,3 +48,7 @@ em {
min-height:calc(100% - 2.47rem);
}
}
.hidden {
display: none;
}

4
client/styles/mixin.scss Normal file
View File

@ -0,0 +1,4 @@
@mixin row-width-limit {
max-width: 11.7rem;
min-width: 9.7rem;
}

View File

@ -1,35 +0,0 @@
### 1.User
- /user/get //获取用户个人信息
- /user/list //获取用户列表,需要提供分页功能
- /user/del //删除用户
- /user/up //更新用户个人信息
- /uesr/login //登录
- /user/reg //注册
- /user/login/status //获取用户登录状态
### 2.Group
- /group/list //获取项目分组列表
- /group/add //添加
- /group/up //更新
- /group/del //删除
### 3.Project
- /project/list/:group_id
- /project/add //添加项目
- /project/up //编辑项目
- /project/del //删除项目
- /project/add_member //添加项目成员
- /project/del_member //删除项目成员
- /project/get //获取一个项目详细信息
### 4.interface
- /interface/list/:project_id
- /interface/add
- /interface/up
- /interface/del
- /interface/mock //执行用户定义的mock返回mock结果
### 5. mock服务器
用户修改host指到yapi服务器yapi服务器收到请求后根据domain/basepath 找到相对应的项目根据req信息找到对应的接口执行用户定义的mock数据返回给用户相应的结果

View File

@ -1,93 +0,0 @@
### 数据库字典
### 数据库基于mongodb
#### 1.User数据表表名:user
```json
{
_id: (int)
username: (string)
password: (sha1)
passsalt: (string)
email : (string)
role : (string)
add_time: (int)
up_time: (int)
}
````
#### 2.Project 数据表,表名:project
```json
{
_id: (int)
uid : (int)
name: (string)
basepath: (string)
desc: (string)
group_id: (int)
members: [
... //成员uid
]
prd_host: (string)//网站上线的domain,可用来获取mock数据
env:(object){
'local环境' : 'http://127.0.0.1'
}
add_time: (int)
up_time: (int)
}
````
#### 3.api 数据表,表名:interface
```json
{
_id: (int)
uid: (int) //负责人uid
path: (string)
group_id: (int)
status: (int)
desc : (string)
add_time: (int)
up_time : (int)
req_headers:(Object){
"header_name":(Object){
default_value: (string),
desc: (string),
mock: (string)
}
}
req_params_type: (form|raw)
req_params: (Object){
"key" : (Object){
default_value: (string),
desc: (string),
mock: (string)
}
}
res_header: (Object){
"header_name":(Object){
default_value: (string),
desc: (string),
mock: (string)
}
}
res_body_type: (text|json),
res_body: (Object){
"key":(Object){
default_value: (string),
desc: (string),
mock: (string)
}
}
}
```
#### 4.项目分组,表名: group
```json
{
_id: (int),
uid: (int),
group_name: (string),
group_desc: (string),
add_time: (int),
up_time: (int)
}
```

View File

@ -1,11 +1,12 @@
const fs = require('fs-extra');
const path = require('path');
const gulp = require('gulp');
const watch = require('gulp-watch');
const babel = require('gulp-babel');
const ora = require('ora');
const chalk = require('chalk');
const { spawn } = require('child_process');
let spinner = ora('请稍等...').start();
let spinner = null;
const DIST = 'server_dist/';
const SRC = 'server/**/*.js';
@ -30,21 +31,18 @@ function generateBabel(status) {
}
function excuteCmd(cmd, args, opts) {
const command = spawn(cmd, args, opts);
const NAME = cmd === 'ykit' ? chalk.cyan('[ykit]') : chalk.blue('[dev-server]');
let command = spawn(cmd, args, opts);
command.stdout.on('data', data => {
output('log', `${cmd}: ${data.toString()}`, true);
output('log', `${NAME} ${data.toString()}`, true);
});
command.stderr.on('data', data => {
output('log', `${cmd}: ${data.toString()}`, true);
output('log', `${NAME} ${data.toString()}`, true);
});
command.on('close', code => {
if (code !== 0) {
output('log', `${cmd}: ${data.toString()}`);
}
});
return command;
}
function output(type, message, restart = false) {
@ -64,25 +62,29 @@ function output(type, message, restart = false) {
}
}
function waitingSpinner() {
spinner = ora({
text: '等待文件变更...',
spinner: 'circleQuarters',
color: 'cyan'
}).start();
}
gulp.task('removeDist', [], function () {
return fs.removeSync(DIST)
});
gulp.task('initialBuild', ['removeDist'], () => {
spinner.text = '初始编译...';
spinner = ora('初始编译...').start();
return gulp.src(SRC)
.pipe(generateBabel())
.pipe(gulp.dest(DIST))
.on('end', () => {
output('success', '初始编译成功!');
spinner = ora({
text: '等待文件变更...',
spinner: 'pong',
color: 'green'
}).start();
waitingSpinner();
excuteCmd('node_modules/.bin/nodemon', ['-q', 'server_dist/app.js', 'dev'], {
excuteCmd('node_modules/.bin/nodemon', ['-q', 'server_dist/app.js'], {
cwd: __dirname
});
@ -94,28 +96,41 @@ gulp.task('initialBuild', ['removeDist'], () => {
gulp.task('default', ['initialBuild'], () => {
gulp.watch(SRC, (event) => {
let originFilePath = path.relative(path.join(__dirname, 'server'), event.path)
let distPath = path.resolve(DIST, path.join(originFilePath))
spinner.text = `正在编译 ${event.path}...`;
gulp.src(event.path).pipe(generateBabel())
.pipe(gulp.dest(DIST)).on('end', () => {
output('success', `成功编译 ${event.path}`);
spinner = ora({
text: 'waiting changes...',
spinner: 'pong',
color: 'green'
});
spinner.start();
.pipe(gulp.dest(path.parse(distPath).dir)).on('end', () => {
output('success', `成功编译 ${originFilePath}`);
output('success', '正在重启服务器...');
waitingSpinner();
});
});
});
gulp.task('buildNode', () => {
return gulp.src(SRC)
.pipe(generateBabel())
.pipe(gulp.dest(DIST));
});
gulp.task('watchNode', ['buildNode'], () => {
return watch(SRC, {
verbose: true,
ignoreInitial: false
})
.pipe(generateBabel())
.pipe(gulp.dest(DIST));
});
gulp.task('build', () => {
let status = {
count: 0
};
let ykitOutput = '';
spinner.text = '正在编译...';
spinner = ora('正在编译,请稍等...').start();
gulp.src(SRC)
.pipe(generateBabel(status))

View File

@ -5,10 +5,11 @@
"main": "index.js",
"scripts": {
"build-server": "babel server -d server_dist",
"dev-server": "nodemon server_dist/app.js",
"install-server" : "node server_dist/install.js",
"dev-server": "nodemon server_dist/app.js -L",
"install-server": "node server_dist/install.js",
"dev": "gulp --silent",
"build": "gulp build --silent"
"build": "gulp build --silent",
"only-watch": "gulp watchNode"
},
"repository": {
"type": "git",
@ -67,7 +68,7 @@
"babel": "^6.5.2",
"babel-cli": "^6.24.1",
"babel-core": "^6.8.0",
"babel-eslint": "^6.0.4",
"babel-eslint": "^7.2.3",
"babel-loader": "^6.2.4",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-object-rest-spread": "^6.8.0",

View File

@ -2,35 +2,22 @@ import yapi from './yapi.js';
import commons from './utils/commons';
yapi.commons = commons;
import dbModule from './utils/db.js';
import mockServer from './middleware/mockServer.js'
import Koa from 'koa'
import convert from 'koa-convert'
import koaStatic from 'koa-static'
import bodyParser from 'koa-bodyparser'
import router from './router.js'
import mockServer from './middleware/mockServer.js';
import Koa from 'koa';
import convert from 'koa-convert';
import koaStatic from 'koa-static';
import bodyParser from 'koa-bodyparser';
import router from './router.js';
yapi.connect = dbModule.connect()
const app = new Koa()
app.use(mockServer)
app.use(bodyParser())
app.use(router.routes())
app.use(router.allowedMethods())
yapi.connect = dbModule.connect();
const app = new Koa();
app.use(mockServer);
app.use(bodyParser());
app.use(router.routes());
app.use(router.allowedMethods());
app.use(koaStatic(
yapi.path.join(yapi.WEBROOT, 'static')
))
app.listen(yapi.WEBCONFIG.port)
commons.log(`the server is start at port ${yapi.WEBCONFIG.port}`)
));
app.listen(yapi.WEBCONFIG.port);
commons.log(`the server is start at port ${yapi.WEBCONFIG.port}`);

View File

@ -1,18 +1,29 @@
module.exports = {
"port": "3000",
import path from 'path'
/**
* config.js是用来第一次安装初始化网站配置如果不用默认的runtime_path可以直接修改runtime_path路径
*/
let runtime_path = path.join(path.resolve(__dirname, '../'), 'runtime')
let config = {
"port": 80,
"runtime_path": runtime_path,
"webhost": "yapi.local.qunar.com",
"adminAccount": "admin@admin.com",
"db": {
"servername": "127.0.0.1",
"DATABASE": "yapi",
"DATABASE": "yapi",
"port": 27017
},
"mail": {
"host": "smtp.mail.com",
"from": "****@mail.com",
"port": 4652,
"auth": {
"user": "****@mail.com",
"pass": "**********"
"user": "****@mail.com",
"pass": "**********"
}
}
}
}
module.exports = config

View File

@ -98,7 +98,7 @@ class userController extends baseController {
})
})
},
tokenField: 'token',
tokenField: 'token'
}
}

View File

@ -1,27 +1,27 @@
import fs from 'fs-extra'
import path from 'path'
import fs from 'fs-extra';
import initConfig from './utils/initConfig.js'
import yapi from './yapi.js';
import commons from './utils/commons';
import dbModule from './utils/db.js';
import userModel from './models/user.js'
import userModel from './models/user.js';
yapi.commons = commons;
yapi.connect = dbModule.connect()
yapi.connect = dbModule.connect();
function install(){
let exist = yapi.commons.fileExist(yapi.path.join(yapi.WEBROOT_RUNTIME, 'init.lock'))
if(exist){
return yapi.commons.log('runtime/init.lock文件已存在请确认您是否已安装。如果需要重新安装请删掉runtime/init.lock文件');
process.exit(1);
function install() {
let exist = yapi.commons.fileExist(yapi.path.join(yapi.WEBROOT_RUNTIME, 'init.lock'));
if (exist) {
yapi.commons.log('runtime/init.lock文件已存在请确认您是否已安装。如果需要重新安装请删掉runtime/init.lock文件');
process.exit(0);
}
setupSql();
}
function setupSql(){
let userInst = yapi.getInst(userModel);
function setupSql() {
let userInst = yapi.getInst(userModel);
let passsalt = yapi.commons.randStr();
let result = userInst.save({
username: yapi.WEBCONFIG.adminAccount.substr(0, yapi.WEBCONFIG.adminAccount.indexOf('@')),
@ -31,15 +31,16 @@ function setupSql(){
role: 'admin',
add_time: yapi.commons.time(),
up_time: yapi.commons.time()
})
result.then(function(success){
});
result.then(function () {
fs.ensureFileSync(yapi.path.join(yapi.WEBROOT_RUNTIME, 'init.lock'));
console.log(`初始化管理员账号 "${yapi.WEBCONFIG.adminAccount}" 成功`);
yapi.fs.ensureFileSync(yapi.path.join(yapi.WEBROOT_RUNTIME, 'init.lock'));
process.exit(1)
}, function(err){
process.exit(0);
}, function (err) {
console.log(`初始化管理员账号 "${yapi.WEBCONFIG.adminAccount}" 失败, ${err.message}`);
process.exit(1)
})
process.exit(0);
});
}
install()
install();

View File

@ -1,24 +1,23 @@
import yapi from '../yapi.js'
import mongoose from 'mongoose'
import autoIncrement from 'mongoose-auto-increment'
import yapi from '../yapi.js';
import mongoose from 'mongoose';
import autoIncrement from 'mongoose-auto-increment';
/**
* 所有的model都需要继承baseModel, 且需要 getSchema和getName方法不然会报错
*/
class baseModel{
constructor(){
this.schema = new mongoose.Schema(this.getSchema())
this.name = this.getName()
this.schema = new mongoose.Schema(this.getSchema());
this.name = this.getName();
if(this.isNeedAutoIncrement() === true){
this.schema.plugin(autoIncrement.plugin, {
model: this.name,
field: this.getPrimaryKey(),
startAt: 101,
incrementBy: yapi.commons.rand(1, 100)
})
});
}
this.model = yapi.db(this.name, this.schema);
@ -32,22 +31,19 @@ class baseModel{
* 可通过覆盖此方法生成其他自增字段
*/
getPrimaryKey(){
return '_id'
return '_id';
}
/**
* 获取collection的schema结构
*/
getSchema(){
yapi.commons.log('Model Class need getSchema function', 'error')
yapi.commons.log('Model Class need getSchema function', 'error');
}
getName(){
yapi.commons.log('Model Class need name', 'error')
yapi.commons.log('Model Class need name', 'error');
}
}
module.exports = baseModel;

View File

@ -1,57 +1,61 @@
import yapi from '../yapi.js'
import mongoose from 'mongoose'
import baseModel from './base.js'
import yapi from '../yapi.js';
import mongoose from 'mongoose';
import baseModel from './base.js';
class groupModel extends baseModel{
getName(){
return 'group'
class groupModel extends baseModel {
getName() {
return 'group';
}
getSchema(){
getSchema() {
return {
uid: Number,
group_name: String,
group_desc: String,
add_time: Number,
up_time: Number
}
};
}
save(data) {
let m = new this.model(data);
return m.save();
}
checkRepeat(name) {
checkRepeat(name) {
return this.model.count({
group_name: name
})
});
}
list(){
return this.model.find().select("group_name _id group_desc add_time up_time").exec()
list() {
return this.model.find().select("group_name _id group_desc add_time up_time").exec();
}
del (id) {
del(id) {
return this.model.deleteOne({
_id: id
})
});
}
up (id, data) {
return this.model.update({
_id: id,
}, {
group_name: data.group_name,
group_desc: data.group_desc,
up_time: yapi.commons.time()
})
up(id, data) {
return this.model.update(
{
_id: id
}, {
group_name: data.group_name,
group_desc: data.group_desc,
up_time: yapi.commons.time()
}
);
}
search(keyword) {
return this.model.find({
group_name: new RegExp(keyword, 'i')
})
.limit(10)
.limit(10);
}
}
module.exports= groupModel
module.exports = groupModel;

View File

@ -1,38 +1,38 @@
import yapi from '../yapi.js'
import baseModel from './base.js'
import yapi from '../yapi.js';
import baseModel from './base.js';
class interfaceModel extends baseModel{
getName(){
return 'interface'
class interfaceModel extends baseModel {
getName() {
return 'interface';
}
getSchema(){
getSchema() {
return {
title: {type: String, required: true},
uid: {type: Number, required: true},
path: {type: String, required: true},
method: {type: String, required: true},
project_id: {type: Number, required: true},
title: { type: String, required: true },
uid: { type: Number, required: true },
path: { type: String, required: true },
method: { type: String, required: true },
project_id: { type: Number, required: true },
desc: String,
add_time: Number,
up_time: Number,
req_headers: [{
name: String, value: String, desc: String, required: Boolean
name: String, value: String, desc: String, required: Boolean
}],
req_params_type: {
type: String,
enum: ["form", "json", "text", "xml"]
enum: ['form', 'json', 'text', 'xml']
},
req_params_form: [{
name: String, value: String,value_type: {type: String, enum: ["text", "file"]}, desc: String, required: Boolean
name: String, value: String, value_type: { type: String, enum: ['text', 'file'] }, desc: String, required: Boolean
}],
req_params_other: String,
res_body_type: {
type: String,
enum: ["json", "text", "xml"]
enum: ['json', 'text', 'xml']
},
res_body: String
}
};
}
save(data) {
@ -40,50 +40,53 @@ class interfaceModel extends baseModel{
return m.save();
}
get(id){
get(id) {
return this.model.findOne({
_id: id
}).exec()
})
.exec();
}
getByPath(project_id, path){
getByPath(project_id, path) {
return this.model.find({
project_id: project_id,
path: path
}).exec()
})
.exec();
}
checkRepeat(id, path, method){
checkRepeat(id, path, method) {
return this.model.count({
project_id: id,
path: path,
method: method
})
});
}
countByProjectId(id){
countByProjectId(id) {
return this.model.count({
project_id: id
})
});
}
list (project_id){
list(project_id) {
return this.model.find({
project_id: project_id
}).sort({_id: -1}).exec()
})
.sort({ _id: -1 })
.exec();
}
del(id){
del(id) {
return this.model.deleteOne({
_id: id
})
});
}
up(id, data){
up(id, data) {
data.up_time = yapi.commons.time();
return this.model.update({
_id: id,
}, data, { runValidators: true })
}, data, { runValidators: true });
}
}

View File

@ -9,13 +9,13 @@ class logModel extends baseModel {
getSchema() {
return {
uid: {type: Number, required: true},
title: {type: String, required: true},
type: {type: String, enum:['user', 'group', 'interface', 'project', 'other'], required: true},
content: {type: String, required: true},
username: {type: String, required: true},
uid: { type: Number, required: true },
title: { type: String, required: true },
type: { type: String, enum: ['user', 'group', 'interface', 'project', 'other'], required: true },
content: { type: String, required: true },
username: { type: String, required: true },
add_time: Number
}
};
}
/**
@ -36,21 +36,27 @@ class logModel extends baseModel {
add_time: yapi.commons.time()
};
let log = new this.model(saveData);
return log.save();
}
list (uid){
list(uid) {
return this.model.find({
uid: uid
}).exec()
})
.exec();
}
listWithPaging(uid, page, limit) {
page = parseInt(page);
limit = parseInt(limit);
return this.model.find({
uid: uid
}).skip((page - 1) * limit).limit(limit).exec();
})
.skip((page - 1) * limit)
.limit(limit)
.exec();
}
listCount(uid) {

View File

@ -1,33 +1,34 @@
import yapi from '../yapi.js'
import baseModel from './base.js'
import yapi from '../yapi.js';
import baseModel from './base.js';
class projectModel extends baseModel{
getName(){
return 'project'
class projectModel extends baseModel {
getName() {
return 'project';
}
getSchema(){
getSchema() {
return {
uid: {type: Number, required: true},
name: {type: String, required: true},
basepath: {type: String, required: true, validate: {
validator: (v) => {
console.log('basepath: ', v)
return v && v[0] === '/'
},
message: 'basepath必须是/开头'
}},
uid: { type: Number, required: true },
name: { type: String, required: true },
basepath: {
type: String, required: true, validate: {
validator: (v) => {
return v && v[0] === '/';
},
message: 'basepath必须是/开头'
}
},
desc: String,
group_id: {type: Number, required: true},
members: Array,
protocol: {type: String, required: true},
prd_host: {type: String, required: true},
group_id: { type: Number, required: true },
members: Array,
protocol: { type: String, required: true },
prd_host: { type: String, required: true },
env: [
{name: String, domain: String}
{ name: String, domain: String }
],
add_time: Number,
up_time: Number
}
};
}
save(data) {
@ -35,37 +36,35 @@ class projectModel extends baseModel{
return m.save();
}
get(id){
get(id) {
return this.model.findOne({
_id: id
}).exec()
}).exec();
}
getByDomain(domain){
getByDomain(domain) {
return this.model.find({
prd_host: domain
}).exec()
}).exec();
}
checkNameRepeat(name){
checkNameRepeat(name) {
return this.model.count({
name: name
})
});
}
checkDomainRepeat(domain, basepath){
checkDomainRepeat(domain, basepath) {
return this.model.count({
prd_host: domain,
basepath: basepath
})
});
}
list (group_id){
list(group_id) {
return this.model.find({
group_id: group_id
}).sort({_id: -1}).exec()
}).sort({ _id: -1 }).exec();
}
listWithPaging(group_id, page, limit) {
@ -73,7 +72,7 @@ class projectModel extends baseModel{
limit = parseInt(limit);
return this.model.find({
group_id: group_id
}).sort({_id: -1}).skip((page - 1) * limit).limit(limit).exec();
}).sort({ _id: -1 }).skip((page - 1) * limit).limit(limit).exec();
}
listCount(group_id) {
@ -82,54 +81,58 @@ class projectModel extends baseModel{
});
}
countByGroupId(group_id){
countByGroupId(group_id) {
return this.model.count({
group_id: group_id
})
});
}
del(id){
del(id) {
return this.model.deleteOne({
_id: id
})
});
}
up(id, data){
up(id, data) {
data.up_time = yapi.commons.time();
return this.model.update({
_id: id,
}, data, { runValidators: true })
}, data, { runValidators: true });
}
addMember(id, uid){
return this.model.update({
_id: id
}, {
$push: {members: uid}
})
addMember(id, uid) {
return this.model.update(
{
_id: id
}, {
$push: { members: uid }
}
);
}
delMember(id, uid){
return this.model.update({
_id: id
}, {
$pull: {members: uid}
})
delMember(id, uid) {
return this.model.update(
{
_id: id
}, {
$pull: { members: uid }
}
);
}
checkMemberRepeat(id, uid){
checkMemberRepeat(id, uid) {
return this.model.count({
_id: id,
members:[uid]
})
members: [uid]
});
}
search(keyword) {
return this.model.find({
name: new RegExp(keyword, 'ig')
})
.limit(10)
.limit(10);
}
}
module.exports = projectModel;

View File

@ -1,87 +1,95 @@
import yapi from '../yapi.js'
import mongoose from 'mongoose'
import baseModel from './base.js'
import baseModel from './base.js';
class userModel extends baseModel{
getName(){
return 'user'
class userModel extends baseModel {
getName() {
return 'user';
}
getSchema(){
return{
username: {
getSchema() {
return {
username: {
type: String,
required: true
},
password:{
type:String,
required: true
},
email: {
type: String,
required: true
},
passsalt: String,
role: String,
add_time: Number,
up_time: Number
}
required: true
},
password: {
type: String,
required: true
},
email: {
type: String,
required: true
},
passsalt: String,
role: String,
add_time: Number,
up_time: Number
};
}
save(data){
save(data) {
let user = new this.model(data);
return user.save();
}
checkRepeat(email){
checkRepeat(email) {
return this.model.count({
email: email
})
});
}
list(){
return this.model.find().select("_id username email role add_time up_time").exec() //显示id name email role
list() {
return this.model.find().select('_id username email role add_time up_time').exec(); //显示id name email role
}
findByUids(uids){
findByUids(uids) {
return this.model.find({
_id: {$in: uids}
}).select("_id username email role add_time up_time").exec()
_id: { $in: uids }
}).select('_id username email role add_time up_time').exec();
}
listWithPaging(page, limit) {
page = parseInt(page);
limit = parseInt(limit);
return this.model.find().sort({_id: -1}).skip((page - 1) * limit).limit(limit).select("_id username email role add_time up_time").exec();
return this.model.find().sort({ _id: -1 }).skip((page - 1) * limit).limit(limit).select('_id username email role add_time up_time').exec();
}
listCount() {
return this.model.count();
}
findByEmail(email){
return this.model.findOne({email: email})
findByEmail(email) {
return this.model.findOne({ email: email });
}
findById(id){
findById(id) {
return this.model.findById({
_id: id
})
});
}
del (id) {
del(id) {
return this.model.deleteOne({
_id: id
})
_id: id
});
}
update(id,data){
update(id, data) {
return this.model.update({
_id: id
}, data)
}, data);
}
search(keyword) {
return this.model.find({
$or: [
{ email: new RegExp(keyword, 'i') },
{ username: new RegExp(keyword, 'i')}
{ username: new RegExp(keyword, 'i') }
]
}, {
passsalt: 0,
password: 0
}).limit(10)
}).limit(10);
}
}
module.exports = userModel
module.exports = userModel;

View File

@ -1,12 +1,11 @@
import koaRouter from 'koa-router'
import interfaceController from './controllers/interface.js'
import groupController from './controllers/group.js'
import userController from './controllers/user.js'
import yapi from './yapi.js'
import projectController from './controllers/project.js'
import logController from './controllers/log.js'
import koaRouter from 'koa-router';
import interfaceController from './controllers/interface.js';
import groupController from './controllers/group.js';
import userController from './controllers/user.js';
import yapi from './yapi.js';
import projectController from './controllers/project.js';
import logController from './controllers/log.js';
const router = koaRouter();
@ -34,47 +33,46 @@ const INTERFACE_CONFIG = {
};
//group
createAction('group', 'list', 'get', 'list')
createAction('group', 'add', 'post', 'add')
createAction('group', 'up', 'post', 'up')
createAction('group', 'del', 'post', 'del')
createAction('group', 'list', 'get', 'list');
createAction('group', 'add', 'post', 'add');
createAction('group', 'up', 'post', 'up');
createAction('group', 'del', 'post', 'del');
//user
createAction('user', 'login', 'post', 'login')
createAction('user', 'reg', 'post', 'reg')
createAction('user', 'list', 'get', 'list')
createAction('user', 'find', 'get', 'findById')
createAction('user', 'update', 'post', 'update')
createAction('user', 'del', 'post', 'del')
createAction('user', 'status', 'get', 'getLoginStatus')
createAction('user', 'logout', 'get', 'logout')
createAction('user', 'login_by_token', 'post', 'loginByToken')
createAction('user', 'change_password', 'post', 'changePassword')
createAction('user', 'search', 'get', 'search')
createAction('user', 'login', 'post', 'login');
createAction('user', 'reg', 'post', 'reg');
createAction('user', 'list', 'get', 'list');
createAction('user', 'find', 'get', 'findById');
createAction('user', 'update', 'post', 'update');
createAction('user', 'del', 'post', 'del');
createAction('user', 'status', 'get', 'getLoginStatus');
createAction('user', 'logout', 'get', 'logout');
createAction('user', 'login_by_token', 'post', 'loginByToken');
createAction('user', 'change_password', 'post', 'changePassword');
createAction('user', 'search', 'get', 'search');
//project
createAction('project', 'add', 'post', 'add')
createAction('project', 'list', 'get', 'list')
createAction('project', 'get', 'get', 'get')
createAction('project', 'up', 'post', 'up')
createAction('project', 'del', 'post', 'del')
createAction('project', 'add_member', 'post', 'addMember')
createAction('project', 'del_member', 'post', 'delMember')
createAction('project', 'get_member_list', 'get', 'getMemberList')
createAction('project', 'search', 'get', 'search')
createAction('project', 'add', 'post', 'add');
createAction('project', 'list', 'get', 'list');
createAction('project', 'get', 'get', 'get');
createAction('project', 'up', 'post', 'up');
createAction('project', 'del', 'post', 'del');
createAction('project', 'add_member', 'post', 'addMember');
createAction('project', 'del_member', 'post', 'delMember');
createAction('project', 'get_member_list', 'get', 'getMemberList');
createAction('project', 'search', 'get', 'search');
//interface
createAction('interface', 'add', 'post', 'add')
createAction('interface', 'list', 'get', 'list')
createAction('interface', 'get', 'get', 'get')
createAction('interface', 'up', 'post', 'up')
createAction('interface', 'del', 'post', 'del')
createAction('interface', 'add', 'post', 'add');
createAction('interface', 'list', 'get', 'list');
createAction('interface', 'get', 'get', 'get');
createAction('interface', 'up', 'post', 'up');
createAction('interface', 'del', 'post', 'del');
//node
createAction('log', 'list', 'get', 'list');
/**
*
* @param {*} controller controller_name
@ -82,18 +80,20 @@ createAction('log', 'list', 'get', 'list');
* @param {*} method request_method , post get put delete ...
* @param {*} action controller_action_name
*/
function createAction(controller, path, method, action){
function createAction(controller, path, method, action) {
router[method](INTERFACE_CONFIG[controller].prefix + path, async (ctx) => {
let inst = new INTERFACE_CONFIG[controller].controller(ctx);
await inst.init(ctx);
if(inst.$auth === true){
if (inst.$auth === true) {
await inst[action].call(inst, ctx);
}else{
} else {
ctx.body = yapi.commons.resReturn(null, 400, 'Without Permission.');
}
})
});
}
module.exports = router
module.exports = router;

View File

@ -1,35 +1,36 @@
import fs from 'fs-extra'
import path from 'path'
import yapi from '../yapi.js'
import sha1 from 'sha1'
import fs from 'fs-extra';
import path from 'path';
import yapi from '../yapi.js';
import sha1 from 'sha1';
exports.resReturn = (data, num, errmsg) => {
num = num || 0;
return {
errcode: num,
errmsg: errmsg || 'success',
data: data
}
}
const MSGTYPE = {
'log': 'Log',
'warn': 'warning',
'error': 'Error'
}
};
};
exports.log = (msg, type) => {
if (!msg) return;
if (!msg) {
return;
}
type = type || 'log';
let f;
switch (type) {
case 'log': f = console.log; break;
case 'warn': f = console.warn; break;
case 'error': f = console.error; break;
default: f = console.log; break;
}
f(type + ':', msg);
let date = new Date();
let year = date.getFullYear();
let month = date.getMonth();
@ -40,14 +41,13 @@ exports.log = (msg, type) => {
if (msg instanceof Error) msg = msg.message;
else msg = JSON.stringify(msg);
}
let data = (new Date).toLocaleTimeString() + "\t|\t" + type + "\t|\t" + msg;
let data = (new Date).toLocaleTimeString() + '\t|\t' + type + '\t|\t' + msg;
fs.writeFileSync(logfile, data, {
flag: 'w+'
});
}
};
exports.fileExist = (filePath) => {
try {
@ -55,80 +55,88 @@ exports.fileExist = (filePath) => {
} catch (err) {
return false;
}
}
};
exports.time = () => {
return Date.parse(new Date()) / 1000;
}
};
exports.fieldSelect = (data, field) => {
if (!data || !field || !Array.isArray(field)) return null;
if (!data || !field || !Array.isArray(field)) {
return null;
}
var arr = {};
field.forEach((f) => {
data[f] && (arr[f] = data[f]);
})
});
return arr;
}
};
exports.rand = (min, max) => {
return Math.floor(Math.random() * (max - min) + min);
}
};
exports.json_parse = (json) => {
try {
return JSON.parse(json);
} catch (e) {
return json
return json;
}
}
};
exports.randStr = () => {
return Math.random().toString(36).substr(2)
}
return Math.random().toString(36).substr(2);
};
exports.generatePassword = (password, passsalt) => {
return sha1(password + sha1(passsalt));
}
};
exports.expireDate = (day) => {
let date = new Date();
date.setTime(date.getTime() + day * 86400000);
return date;
}
};
exports.sendMail = (options, cb) => {
if (!yapi.mail) return false;
options.subject = options.subject ? options.subject + '-yapi平台' : 'ypai平台';
cb = cb || function (err, info) {
cb = cb || function (err) {
if (err) {
yapi.commons.log('send mail ' + options.to + ' error,' + err.message, 'error');
} else {
yapi.commons.log('send mail ' + options.to + ' success');
}
};
}
try {
yapi.mail.sendMail({
from: yapi.WEBCONFIG.mail.auth.user,
from: yapi.WEBCONFIG.mail.from,
to: options.to,
subject: 'yapi平台',
html: options.contents
}, cb)
}, cb);
} catch (e) {
console.error(e.message)
console.error(e.message);
}
}
};
exports.validateSearchKeyword = keyword => {
if (/^\*|\?|\+|\$|\^|\\|\.$/.test(keyword)) {
return false;
}
return true;
}
};
exports.filterRes = (list, rules) => {
return list.map(item => {
let filteredRes = {};
rules.forEach(rule => {
if (typeof rule == 'string') {
filteredRes[rule] = item[rule];
@ -136,38 +144,51 @@ exports.filterRes = (list, rules) => {
filteredRes[rule.alias] = item[rule.key];
}
});
return filteredRes;
})
}
});
};
exports.verifyPath = (path) => {
if (/^\/[a-zA-Z0-9\-\/_:\.]+$/.test(path)) {
if (path[path.length - 1] === '/') {
return false;
} else {
return true
return true;
}
} else {
return false;
}
}
};
function trim(str) {
if (!str) return str;
if (!str) {
return str;
}
str = str + '';
return str.replace(/(^\s*)|(\s*$)/g, "");
return str.replace(/(^\s*)|(\s*$)/g, '');
}
function ltrim(str) {
if (!str) return str;
if (!str) {
return str;
}
str = str + '';
return str.replace(/(^\s*)/g, "");
return str.replace(/(^\s*)/g, '');
}
function rtrim(str) {
if (!str) return str;
if (!str) {
return str;
}
str = str + '';
return str.replace(/(\s*$)/g, "");
return str.replace(/(\s*$)/g, '');
}
exports.trim = trim;
@ -175,7 +196,10 @@ exports.ltrim = ltrim;
exports.rtrim = rtrim;
exports.handleParams = (params, keys) => {
if (!params || typeof params !== 'object' || !keys || typeof keys !== 'object') return false;
if (!params || typeof params !== 'object' || !keys || typeof keys !== 'object') {
return false;
}
for (var key in keys) {
var filter = keys[key];
if (params[key]) {
@ -186,5 +210,6 @@ exports.handleParams = (params, keys) => {
}
}
}
return params;
}
};

View File

@ -1,31 +1,35 @@
import mongoose from 'mongoose'
import yapi from '../yapi.js'
import autoIncrement from 'mongoose-auto-increment'
import mongoose from 'mongoose';
import yapi from '../yapi.js';
import autoIncrement from 'mongoose-auto-increment';
function model(model, schema){
if(schema instanceof mongoose.Schema === false){
schema = new mongoose.Schema(schema);
}
schema.set('autoIndex', false);
return yapi.connect.model(model, schema, model)
return yapi.connect.model(model, schema, model);
}
function connect(){
mongoose.Promise = global.Promise;
let config = yapi.WEBCONFIG;
let options = {};
if(config.user){
options.user = config.db.user,
options.pass = config.db.pass
options.pass = config.db.pass;
}
let db = mongoose.connect(`mongodb://${config.db.servername}:${config.db.port}/${config.db.DATABASE}`, options);
db.then(function (res) {
yapi.commons.log('mongodb load success...')
yapi.commons.log('mongodb load success...');
}, function (err) {
yapi.commons.log(err, 'Mongo connect error');
})
});
autoIncrement.initialize(db);
return db;
@ -33,12 +37,7 @@ function connect(){
yapi.db = model;
module.exports = {
model: model,
connect: connect
};
};

View File

@ -1,10 +1,13 @@
import path from 'path'
import fs from 'fs-extra'
import config from '../config.js'
import path from 'path';
import fs from 'fs-extra';
import config from '../config.js';
let configPath = path.join(path.resolve(__dirname, '../../'), 'runtime', 'config.json')
let runtimePath = config.runtime_path;
fs.ensureDirSync( runtimePath );
fs.ensureDirSync( path.join(runtimePath, 'log'));
let configPath = path.join(runtimePath, 'config.json')
fs.writeFileSync(configPath,
JSON.stringify(config, null, "\t"),
{encoding: 'utf8'}
)
JSON.stringify(config, null, '\t'),
{ encoding: 'utf8' }
);

View File

@ -1,24 +1,22 @@
import path from 'path'
import fs from 'fs-extra'
import path from 'path';
import fs from 'fs-extra';
import nodemailer from 'nodemailer';
import config from '../runtime/config.json'
import config from '../runtime/config.json';
var insts = new Map();
let insts = new Map();
let mail;
const WEBROOT = path.resolve(__dirname, '..'); //路径
const WEBROOT_SERVER = __dirname;
const WEBROOT_RUNTIME = path.join(WEBROOT, 'runtime');
const WEBROOT_RUNTIME = config.runtime_path;
const WEBROOT_LOG = path.join(WEBROOT_RUNTIME, 'log');
const WEBCONFIG = config;
const WEBCONFIG = config;
fs.ensureDirSync(WEBROOT_RUNTIME);
fs.ensureDirSync(WEBROOT_LOG);
if(WEBCONFIG.mail){
mail = nodemailer.createTransport(WEBCONFIG.mail)
if (WEBCONFIG.mail) {
mail = nodemailer.createTransport(WEBCONFIG.mail);
}
/**
@ -27,18 +25,18 @@ if(WEBCONFIG.mail){
* @example
* yapi.getInst(groupModel, arg1, arg2)
*/
function getInst(m, ...args){
if(!insts.get(m)){
insts.set(m, new m(args))
function getInst(m, ...args) {
if (!insts.get(m)) {
insts.set(m, new m(args));
}
return insts.get(m)
return insts.get(m);
}
function delInst(m){
try{
insts.delete(m)
}catch(err){
console.error(err)
function delInst(m) {
try {
insts.delete(m);
} catch (err) {
console.error(err);
}
}
@ -53,6 +51,6 @@ let r = {
getInst: getInst,
delInst: delInst,
getInsts: insts
}
if(mail) r.mail = mail;
};
if (mail) r.mail = mail;
module.exports = r;

View File

@ -43,6 +43,7 @@ _yapi2.default.commons = _commons2.default;
_yapi2.default.connect = _db2.default.connect();
var app = new _koa2.default();
app.use(_mockServer2.default);
app.use((0, _koaBodyparser2.default)());
app.use(_router2.default.routes());

View File

@ -1,7 +1,18 @@
"use strict";
'use strict';
module.exports = {
"port": "3000",
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* config.js是用来第一次安装初始化网站配置如果不用默认的runtime_path可以直接修改runtime_path路径
*/
var runtime_path = _path2.default.join(_path2.default.resolve(__dirname, '../'), 'runtime');
var config = {
"port": 80,
"runtime_path": runtime_path,
"webhost": "yapi.local.qunar.com",
"adminAccount": "admin@admin.com",
"db": {
@ -11,10 +22,13 @@ module.exports = {
},
"mail": {
"host": "smtp.mail.com",
"from": "****@mail.com",
"port": 4652,
"auth": {
"user": "****@mail.com",
"pass": "**********"
}
}
};
};
module.exports = config;

View File

@ -4,10 +4,6 @@ var _fsExtra = require('fs-extra');
var _fsExtra2 = _interopRequireDefault(_fsExtra);
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
var _initConfig = require('./utils/initConfig.js');
var _initConfig2 = _interopRequireDefault(_initConfig);
@ -35,10 +31,12 @@ _yapi2.default.connect = _db2.default.connect();
function install() {
var exist = _yapi2.default.commons.fileExist(_yapi2.default.path.join(_yapi2.default.WEBROOT_RUNTIME, 'init.lock'));
if (exist) {
return _yapi2.default.commons.log('runtime/init.lock文件已存在请确认您是否已安装。如果需要重新安装请删掉runtime/init.lock文件');
process.exit(1);
_yapi2.default.commons.log('runtime/init.lock文件已存在请确认您是否已安装。如果需要重新安装请删掉runtime/init.lock文件');
process.exit(0);
}
setupSql();
}
@ -54,13 +52,14 @@ function setupSql() {
add_time: _yapi2.default.commons.time(),
up_time: _yapi2.default.commons.time()
});
result.then(function (success) {
result.then(function () {
_fsExtra2.default.ensureFileSync(_yapi2.default.path.join(_yapi2.default.WEBROOT_RUNTIME, 'init.lock'));
console.log('\u521D\u59CB\u5316\u7BA1\u7406\u5458\u8D26\u53F7 "' + _yapi2.default.WEBCONFIG.adminAccount + '" \u6210\u529F');
_yapi2.default.fs.ensureFileSync(_yapi2.default.path.join(_yapi2.default.WEBROOT_RUNTIME, 'init.lock'));
process.exit(1);
process.exit(0);
}, function (err) {
console.log('\u521D\u59CB\u5316\u7BA1\u7406\u5458\u8D26\u53F7 "' + _yapi2.default.WEBCONFIG.adminAccount + '" \u5931\u8D25, ' + err.message);
process.exit(1);
process.exit(0);
});
}

View File

@ -32,6 +32,7 @@ var baseModel = function () {
this.schema = new _mongoose2.default.Schema(this.getSchema());
this.name = this.getName();
if (this.isNeedAutoIncrement() === true) {
this.schema.plugin(_mongooseAutoIncrement2.default.plugin, {
model: this.name,

View File

@ -60,15 +60,15 @@ var interfaceModel = function (_baseModel) {
}],
req_params_type: {
type: String,
enum: ["form", "json", "text", "xml"]
enum: ['form', 'json', 'text', 'xml']
},
req_params_form: [{
name: String, value: String, value_type: { type: String, enum: ["text", "file"] }, desc: String, required: Boolean
name: String, value: String, value_type: { type: String, enum: ['text', 'file'] }, desc: String, required: Boolean
}],
req_params_other: String,
res_body_type: {
type: String,
enum: ["json", "text", "xml"]
enum: ['json', 'text', 'xml']
},
res_body: String
};

View File

@ -127,6 +127,7 @@ var logModel = function (_baseModel) {
value: function listWithPaging(uid, page, limit) {
page = parseInt(page);
limit = parseInt(limit);
return this.model.find({
uid: uid
}).skip((page - 1) * limit).limit(limit).exec();

View File

@ -49,13 +49,14 @@ var projectModel = function (_baseModel) {
return {
uid: { type: Number, required: true },
name: { type: String, required: true },
basepath: { type: String, required: true, validate: {
basepath: {
type: String, required: true, validate: {
validator: function validator(v) {
console.log('basepath: ', v);
return v && v[0] === '/';
},
message: 'basepath必须是/开头'
} },
}
},
desc: String,
group_id: { type: Number, required: true },
members: Array,

View File

@ -20,14 +20,6 @@ var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
var _yapi = require('../yapi.js');
var _yapi2 = _interopRequireDefault(_yapi);
var _mongoose = require('mongoose');
var _mongoose2 = _interopRequireDefault(_mongoose);
var _base = require('./base.js');
var _base2 = _interopRequireDefault(_base);
@ -85,21 +77,21 @@ var userModel = function (_baseModel) {
}, {
key: 'list',
value: function list() {
return this.model.find().select("_id username email role add_time up_time").exec(); //显示id name email role
return this.model.find().select('_id username email role add_time up_time').exec(); //显示id name email role
}
}, {
key: 'findByUids',
value: function findByUids(uids) {
return this.model.find({
_id: { $in: uids }
}).select("_id username email role add_time up_time").exec();
}).select('_id username email role add_time up_time').exec();
}
}, {
key: 'listWithPaging',
value: function listWithPaging(page, limit) {
page = parseInt(page);
limit = parseInt(limit);
return this.model.find().sort({ _id: -1 }).skip((page - 1) * limit).limit(limit).select("_id username email role add_time up_time").exec();
return this.model.find().sort({ _id: -1 }).skip((page - 1) * limit).limit(limit).select('_id username email role add_time up_time').exec();
}
}, {
key: 'listCount',

View File

@ -28,6 +28,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
exports.resReturn = function (data, num, errmsg) {
num = num || 0;
return {
errcode: num,
errmsg: errmsg || 'success',
@ -35,16 +36,15 @@ exports.resReturn = function (data, num, errmsg) {
};
};
var MSGTYPE = {
'log': 'Log',
'warn': 'warning',
'error': 'Error'
};
exports.log = function (msg, type) {
if (!msg) return;
if (!msg) {
return;
}
type = type || 'log';
var f = void 0;
switch (type) {
case 'log':
f = console.log;break;
@ -55,7 +55,9 @@ exports.log = function (msg, type) {
default:
f = console.log;break;
}
f(type + ':', msg);
var date = new Date();
var year = date.getFullYear();
var month = date.getMonth();
@ -65,7 +67,9 @@ exports.log = function (msg, type) {
if ((typeof msg === 'undefined' ? 'undefined' : (0, _typeof3.default)(msg)) === 'object') {
if (msg instanceof Error) msg = msg.message;else msg = (0, _stringify2.default)(msg);
}
var data = new Date().toLocaleTimeString() + "\t|\t" + type + "\t|\t" + msg;
var data = new Date().toLocaleTimeString() + '\t|\t' + type + '\t|\t' + msg;
_fsExtra2.default.writeFileSync(logfile, data, {
flag: 'w+'
});
@ -84,11 +88,16 @@ exports.time = function () {
};
exports.fieldSelect = function (data, field) {
if (!data || !field || !Array.isArray(field)) return null;
if (!data || !field || !Array.isArray(field)) {
return null;
}
var arr = {};
field.forEach(function (f) {
data[f] && (arr[f] = data[f]);
});
return arr;
};
@ -121,16 +130,18 @@ exports.expireDate = function (day) {
exports.sendMail = function (options, cb) {
if (!_yapi2.default.mail) return false;
options.subject = options.subject ? options.subject + '-yapi平台' : 'ypai平台';
cb = cb || function (err, info) {
cb = cb || function (err) {
if (err) {
_yapi2.default.commons.log('send mail ' + options.to + ' error,' + err.message, 'error');
} else {
_yapi2.default.commons.log('send mail ' + options.to + ' success');
}
};
try {
_yapi2.default.mail.sendMail({
from: _yapi2.default.WEBCONFIG.mail.auth.user,
from: _yapi2.default.WEBCONFIG.mail.from,
to: options.to,
subject: 'yapi平台',
html: options.contents
@ -144,12 +155,14 @@ exports.validateSearchKeyword = function (keyword) {
if (/^\*|\?|\+|\$|\^|\\|\.$/.test(keyword)) {
return false;
}
return true;
};
exports.filterRes = function (list, rules) {
return list.map(function (item) {
var filteredRes = {};
rules.forEach(function (rule) {
if (typeof rule == 'string') {
filteredRes[rule] = item[rule];
@ -157,6 +170,7 @@ exports.filterRes = function (list, rules) {
filteredRes[rule.alias] = item[rule.key];
}
});
return filteredRes;
});
};
@ -174,21 +188,33 @@ exports.verifyPath = function (path) {
};
function trim(str) {
if (!str) return str;
if (!str) {
return str;
}
str = str + '';
return str.replace(/(^\s*)|(\s*$)/g, "");
return str.replace(/(^\s*)|(\s*$)/g, '');
}
function ltrim(str) {
if (!str) return str;
if (!str) {
return str;
}
str = str + '';
return str.replace(/(^\s*)/g, "");
return str.replace(/(^\s*)/g, '');
}
function rtrim(str) {
if (!str) return str;
if (!str) {
return str;
}
str = str + '';
return str.replace(/(\s*$)/g, "");
return str.replace(/(\s*$)/g, '');
}
exports.trim = trim;
@ -196,7 +222,10 @@ exports.ltrim = ltrim;
exports.rtrim = rtrim;
exports.handleParams = function (params, keys) {
if (!params || (typeof params === 'undefined' ? 'undefined' : (0, _typeof3.default)(params)) !== 'object' || !keys || (typeof keys === 'undefined' ? 'undefined' : (0, _typeof3.default)(keys)) !== 'object') return false;
if (!params || (typeof params === 'undefined' ? 'undefined' : (0, _typeof3.default)(params)) !== 'object' || !keys || (typeof keys === 'undefined' ? 'undefined' : (0, _typeof3.default)(keys)) !== 'object') {
return false;
}
for (var key in keys) {
var filter = keys[key];
if (params[key]) {
@ -210,5 +239,6 @@ exports.handleParams = function (params, keys) {
}
}
}
return params;
};

View File

@ -18,14 +18,18 @@ function model(model, schema) {
if (schema instanceof _mongoose2.default.Schema === false) {
schema = new _mongoose2.default.Schema(schema);
}
schema.set('autoIndex', false);
return _yapi2.default.connect.model(model, schema, model);
}
function connect() {
_mongoose2.default.Promise = global.Promise;
var config = _yapi2.default.WEBCONFIG;
var options = {};
if (config.user) {
options.user = config.db.user, options.pass = config.db.pass;
}

View File

@ -18,6 +18,9 @@ var _config2 = _interopRequireDefault(_config);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var configPath = _path2.default.join(_path2.default.resolve(__dirname, '../../'), 'runtime', 'config.json');
var runtimePath = _config2.default.runtime_path;
_fsExtra2.default.ensureDirSync(runtimePath);
_fsExtra2.default.ensureDirSync(_path2.default.join(runtimePath, 'log'));
var configPath = _path2.default.join(runtimePath, 'config.json');
_fsExtra2.default.writeFileSync(configPath, (0, _stringify2.default)(_config2.default, null, "\t"), { encoding: 'utf8' });
_fsExtra2.default.writeFileSync(configPath, (0, _stringify2.default)(_config2.default, null, '\t'), { encoding: 'utf8' });

View File

@ -27,7 +27,7 @@ var mail = void 0;
var WEBROOT = _path2.default.resolve(__dirname, '..'); //路径
var WEBROOT_SERVER = __dirname;
var WEBROOT_RUNTIME = _path2.default.join(WEBROOT, 'runtime');
var WEBROOT_RUNTIME = _config2.default.runtime_path;
var WEBROOT_LOG = _path2.default.join(WEBROOT_RUNTIME, 'log');
var WEBCONFIG = _config2.default;

39
service.sh Normal file
View File

@ -0,0 +1,39 @@
#!/bin/bash
prog="/var/soft/node-v6.11.1-linux-x64/bin/pm2"
app="/home/q/www/yapi.beta.corp.qunar.com/webapp/server_dist/app.js"
start() {
echo "Starting Server..."
eval "$prog start $app --name=yapi"
}
stop() {
echo "Stopping Server..."
eval "$prog stop yapi"
}
restart() {
echo "Restart Server..."
eval "$prog restart yapi"
}
case "$1" in
start)
start && exit 0
;;
stop)
stop || exit 0
;;
restart)
restart || exit 0
;;
*)
echo $"Usage: $0 {start|stop|restart}"
exit 2
esac

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB