Skip to content

feat(build):基于express纯手工构建整体项目框架(手写egg.js实现。也可从node.js http模块为基础直接构建e… #34

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ bin/Release
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
**/node_modules

dist
#Webstorm metadata
.idea

Expand Down
9 changes: 9 additions & 0 deletions app/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
NODE_ENV=development
SERVER_PORT=3000

MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=your_password
MYSQL_DATABASE=player
PRINT_MYSQL_SQL=1
8 changes: 8 additions & 0 deletions app/.test.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
NODE_ENV=unittest
SERVER_PORT=3000

MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=your_password
MYSQL_DATABASE=player_unittest
19 changes: 19 additions & 0 deletions app/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@


import express from 'express';
import bodyParser from 'body-parser';
import initRouters from './router';
import initORM from './model';

const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
initORM();
initRouters(app);


app.listen(process.env.SERVER_PORT, () => {
console.log("Server is listening on port 3000");
});

export { app };
79 changes: 79 additions & 0 deletions app/base/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@


import { Request, Response } from 'express';
import { join } from 'path';
import { readdirSync } from 'fs';
import { camelCase, upperFirst } from 'lodash';
import { CustomSequelize, sequelize } from '../model';
import IRegisterService from '../service';


export interface IContext {
request: Request;
response: Response;
model: CustomSequelize;
service: IRegisterService;
}


export default class BaseController {
public ctx: IContext;

constructor(req: Request, res: Response) {
const context = {
request: req,
response: res,
model: sequelize.models as any,
};
let service = {} as any;
const path = join(__dirname, '../service');
const serviceFiles = readdirSync(path).filter(x => x !== 'index.js').filter(x => x !== 'index.ts');
serviceFiles.map(fileName => {
const name = upperFirst(camelCase(fileName.split('.')[0] + 'Service'));
const cotr = require(join(path, fileName)).default;
service = {
...service,
get [name]() {
return new cotr(context);
}
}
});

this.ctx = {
...context,
service,
};
}

success(data: any, msg?: string) {
const body = {
errcode: 0,
msg: msg || 'success',
retcode: 0,
data,
};
this.ctx.response.setHeader('Content-Type', 'application/json');
this.ctx.response.send(body);
return;
}

error(
errcode: number,
retcode: number,
msg: Error,
data?: any,
) {
const body = {
errcode, msg, retcode, data,
};
this.ctx.response.send(body);
}

get request() {
return this.ctx.request;
}

get response() {
return this.ctx.response;
}
}
9 changes: 9 additions & 0 deletions app/base/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@


import { IContext } from './controller';

export default class BaseService {
constructor(
public ctx: Omit<IContext, 'service'>,
) { }
}
27 changes: 27 additions & 0 deletions app/cli/controller.ts.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@


import BaseController from "../../base/controller";


export default class {{upperFirst controller_name}}Controller extends BaseController {
// restful规范接口
index() {
this.success('index');
}

show() {
this.success('show');
}

async create() {
this.success('create');
}

update() {
this.success('update');
}

destroy() {
this.success('destroy');
}
}
5 changes: 5 additions & 0 deletions app/cli/route-config.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// RESTFUL
'/api/{{controller_name}}': {
name: '{{upperFirst controller_name}}',
path: '/api/{{controller_name}}'
},
6 changes: 6 additions & 0 deletions app/cli/service.ts.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import BaseService from "../base/server";


export default class {{upperFirst service_name}}Service extends BaseService {

}
51 changes: 51 additions & 0 deletions app/controller/api/player.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* 一个好习惯ajax的调用均使用protocol://hostname/api/** 等的url形式
* 接口异常均可用 this.error 函数返回格式化数据到前端。本例子暂未使用
*/
import BaseController from "../../base/controller";


export default class PlayerController extends BaseController {
// restful规范接口
async index() {
const { ctx } = this;
const result = await ctx.service.PlayerService.publicAll();
this.success(result);
}

async show() {
const { ctx } = this;
const { id } = ctx.request.params;
const result = await ctx.service.PlayerService.fetchOne(Number(id));
this.success(result);
}

async create() {
const { ctx } = this;
const { name, position } = ctx.request.body;
const result = await ctx.service.PlayerService.create({ name, position });
this.success(result);
}

async update() {
const { ctx } = this;
const { id } = ctx.request.params;
const { name, position } = ctx.request.body;
const result = await ctx.service.PlayerService.modify(Number(id), { name, position });
this.success(result.length ? '修改成功' : '未知错误');
}

async destroy() {
const { ctx } = this;
const { id } = ctx.request.params;
const result = await ctx.service.PlayerService.delete(Number(id));
this.success(result);
}

/**
* 非restful规范接口. test
*/
async test() {
this.success('result test');
}
}
3 changes: 3 additions & 0 deletions app/controller/home.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/**
* 可以处理非ajax之类的调用,如模板渲染等的前置加工。未处理
*/
3 changes: 3 additions & 0 deletions app/middleware/authorized.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/**
* 中间件,可用于鉴权以及数据类型校验。时间关系暂不实现
*/
48 changes: 48 additions & 0 deletions app/model/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@


import { Model, Sequelize } from 'sequelize';
import { Player } from './mysql/Player';
import { join } from 'path';
import { readdirSync } from 'fs';



/**
* 注册到框架上下文。key值请与文件名保持一致
*/
export interface CustomSequelize {
Player: typeof Player;
}


export const sequelize = new Sequelize(
process.env.MYSQL_DATABASE!,
process.env.MYSQL_USER!,
process.env.MYSQL_PASSWORD!,
{
host: process.env.MYSQL_HOST!,
logging: false,
dialect: 'mysql',
}
);

const initORM = async () => {
const modelObj: Record<string, typeof Model> = {};
try {
await sequelize.authenticate();
console.log('Connection has been established successfully.');
} catch (error) {
console.error('Unable to connect to the database:', error);
}
const path = join(__dirname, 'mysql');
const modelFiles = readdirSync(path);
modelFiles.length && modelFiles.map(fileName => {
const key = fileName.split('.')[0];
const model = require(join(path, fileName)).default;
modelObj[key] = model();
});
await sequelize.sync();
return modelObj;
};

export default initORM;
33 changes: 33 additions & 0 deletions app/model/mysql/Player.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@


import { Model, INTEGER, STRING } from 'sequelize';
import { sequelize } from '../index';


export class Player extends Model {
declare id: number;
name: string;
position: 'C' | 'PF' | 'SF' | 'PG' | 'SG';
}

export default () => {
return Player.init({
id: {
field: 'FPlayerId',
type: INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: {
field: 'FPlayerName',
type: STRING(20),
},
position: {
field: 'FPlayerPosition',
type: STRING(2),
}
}, {
sequelize,
tableName: 'T_Player',
});
}
35 changes: 30 additions & 5 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,36 @@
"description": "Building a RESTful CRUD API with Node.js, Express/Koa and MongoDB.",
"main": "server.js",
"scripts": {
"start": "NODE_ENV=development node server.js",
"start": "tsc && node -r dotenv/config ./dist/app.js",
"start:prod": "NODE_ENV=production node server.js",
"test": "echo \"Error: no test specified\" && exit 1"
"create:service": "plop create:service",
"create:restful": "plop create:api-controller",
"test": "env-cmd -f ./.test.env --use-shell \"ts-mocha -p ./tsconfig.json test/**/*.test.ts --timeout 60000\""
},
"dependencies": {
"body-parser": "^1.20.0",
"dotenv": "^16.0.0",
"express": "^4.17.1",
"mongoose": "^5.9.2"
"lodash": "^4.17.21",
"mongoose": "^5.9.2",
"mysql2": "^2.3.3",
"sequelize": "^6.19.0",
"typescript": "^4.6.4"
},
"devDependencies": {
"chai": "^4.2.0"
"@types/chai": "^4.3.1",
"@types/express": "^4.17.13",
"@types/lodash": "^4.14.182",
"@types/mocha": "^9.1.1",
"@types/supertest": "^2.0.12",
"chai": "^4.2.0",
"env-cmd": "^10.1.0",
"mocha": "^10.0.0",
"plop": "^3.1.0",
"supertest": "^6.2.3",
"ts-mocha": "^9.0.2",
"ts-node": "^10.7.0",
"tsconfig-paths": "^3.14.1"
},
"engines": {
"node": ">=10.15.0"
Expand All @@ -24,5 +44,10 @@
"not dead",
"not ie <= 11",
"not op_mini all"
]
],
"mocha": {
"require": [
"tsconfig-paths/register"
]
}
}
Loading