preface
In the development of NestJs + Mysql project, you may want to enable the synchronous generation of data tables from entity classes. This method is ok, and typeOrm is good at, but there is a risk that data will be deleted by mistake.
The target
No matter what we want to develop, the first thing we need to be clear is, what do we want this thing to do, what are the functions, and what are the approximate implementation methods of these functions? It would be nice to draw an architecture diagram or flow chart in advance 😁 Here we summarize a few things that we want the tool to do for us:
Data table structure through a database
- Generate the corresponding entity class in reverse
- Generate the corresponding controller in reverse order
- Generate the corresponding service layer in reverse
Thinking about
- I want to like
nest-cli
orvue-cli
These are some excellent onescli
Similarly, a variety of commands can be generated by typing a single command on the command lineFile (Entity, Controller, Services)Because it’s very, very convenient. - I want to take the data table structure specified by the user and convert it to the structure I need.
- User input
v type src
The tool will be based on the data tabletype
generateEntity class, controller, service layerAnd mount the generated folder tosrc
Directory.
start
First of all, let’s look at how to doThe code is run by command
- Let’s start by executing the following command to create a project
npm init -y && mkdir bin && touch ./bin/code-gen && mkdir src && touch ./src/index.ts
- We will be
package.json
Add an attribute tobin
Its value is an object, of an objectkey
is“Instruction”The name of the objectvalue
is“Command execution file”For example:
Two questions can be seen here:
- I specified multiple keys (V, code-gen, nest-code-generate). I want to be clear that these are just aliases. I can use as many names as I want.
- Code-gen seems to be a file with no suffix, neither.js nor.ts, yes this file does have no suffix
- We are in
bin/code-gen
Write something in it
If the computer is using vscode, code-gen is usually not prompted, we use the following figure to solve the problem
#! /usr/bin/env node
console.log('code-gen');
Copy the code
Let’s take a look at the code above:
- The first line
#! /usr/bin/env node
The interpreter used to specify the script file, which we specify herenode
“And then we simply printed something. - So how do we execute this program? This is done on the command line
v
Will we see code-gen printed on the command line? - Obviously not, because we’re missing the last very, very important step, which is execution
npm link
This command. - The effect of this command is to create a locally and quick operation, let you can be on the premise of not publish a package Can experience the function of the package, the information on this command, friends can take a look at the official documentation of NPM, or check the information online Is very simple, if the conflicts in the execution of this order, We can add one
--force
Suffix, force update. - At this point we are executing on the command line
v
You will see the command line print out the code-gen message.
How do I receive user input parameters on the command line?
- We’re going to use a large part here
cli
It’s called a package calledcommander
, the implementation ofnpm i commander
The method of use is also very simple, we will simply use the knowledge here, because I found that the official website said not clear (anyway I can not understand). - Let’s look at the following code:
- The first paragraph:
program.version().usage()
It’s actually very simple when we typev -v
Will print out this number,usage()
Is to performv -h
Time to prompt. - The second paragraph:
program.argument().argument().action()
This code accepts two arguments, one of which istable-name
One is thedir
Among themdir
It’s optional, in the backaction
That’s the callback method, which accepts2The two parameters that we acceptargument
.
#! /usr/bin/env node
const program = require('commander');
program
.version(`nest-code-generate@The ${require(".. /package.json").version}`)
.usage(`
[dir]`
,table_name2...>);
program
.argument('<table-name>'."Data table name")
.argument('[dir]'.'Folder path')
.action((tableName, dir) = > {
console.log(tableName, dir);
});
program.parse(process.argv);
Copy the code
Gets the structure of the data table
With commander we can easily interact with the user from the command line. What should we do if the user enters the v type SRC command? The first thing we need to do is to read the user’s type table. Here we also need to borrow a database connection tool called ali-rds-async, which can be installed by executing NPM I ali-rds-async.
Ali-rds-async use method:
- We first in
src
Create one in the directoryclient
Folder and create oneindex.ts
- Connecting to the database (isn’t that easy)
import AsyncAliRds from "ali-rds-async"; export const db = new AsyncAliRds({ host: 'localhost'.port: 3306.user: 'root'.password: 'password'.database: 'nest-code-generate' }); Copy the code
- Read the table structure: Here we can do this by executing an SQL, but here we need to modify the file to facilitate our testing and coding.
- new
tsconfig.json
; bin/code-gen
Parser is ParserLib (package path)
The introduction of
src/index.ts
: Here is the packaged entry file, exposedParser
package.json
: Add a few herescript
- File modification done, we run first
npm run serve
ornpm run build
Package the file tolib
Folder, and then executev type
Instructions to see what happens? We got it.type
Table structure.
Generate entity class
There is also a command line question and answer mode that is involved in generating entity classes. In this case, we will implement this feature, which allows users to create files selectively like vue-CLI.
-
Use the inquirer tool to implement command line question and answer mode, this tool is relatively simple to use, will be briefly described later
-
Let’s start with a piece of code
import { prompt } from 'inquirer'; import { findPath } from "./utils"; export class Parser { tableName : string; // Table name dir : string; // Generate a pathtype ! : string;// Generate entity class or control layer or service layertargetPath ! : string;// Generate a path constructor(tableName: string, dir: string) { this.tableName = tableName; this.dir = dir; this.prompt(); } // Initiate an inquiry async prompt() { const { type } = await prompt([ { name: 'type'.type: 'list'.message: 'What content is generated? : '.choices: [{name: 'Entity class '.value: 'entity' }, { name: 'Tier (Entity class + Controller and service layer methods)'.value: 'tier' }, { name: 'CURD (Entity class + simple add, Delete, change, check)'.value: 'curd' }, { name: 'All (All generated: Entity class + Controller and service layer methods + simple add, delete, change and query)'.value: 'all'}}]]);this.type = type; // Get the build path const targetPath = findPath(this.dir); this.targetPath = targetPath; this.parseOption(); }}Copy the code
Let’s look at what the prompt method does in this code:
- call
inquirer
theprompt
Method, which initiates a query on the command line, like this;
So let’s look at the transfer to
prompt
So far we’ve only used the first parameter, which is an array, and each item in the array represents a problem, so we’ve only passed one problem, so let’s look at the problemkey
What do they all mean?name
: is the name of the field that this method will return to you later;type
: Indicates the type of the current problemlist
和input
One is the list and one is the input.message
: This is an easy question to ask.choices
: This property is an array, onlytype
forlist
Is available in each of the items in the arrayname
That’s what’s displayed to the user, and the other onevalue
That’s what the user returns to you when they select it.
Let’s look at the actual application. What do we get?
- will
type
Save it, because that’s what the user is going to generate; - Obtain the build path, this method has only one function, is to obtain the user’s final generation path, the default is
src
; - call
parseOption
Methods;
- call
-
The parseOption method lets see what this method does:
async parseOption() { const typeMap: { [k in Options]: () = > any } = { 'entity': () = > this.generateEntity(), 'tier': () = > this.generateTier(), 'curd': () = > this.generateCURD(), 'all': () = > this.generateAll() }; if (this.type && Reflect.has(typeMap, this.type)) { await typeMap[this.type](); } else { await typeMap.entity(); } this.exit(); } Copy the code
- Create a Policy
map
throughtype
To call the corresponding method, if notype
ortype
Not inmap
, the method that generates the instance is directly called; - call
exit()
Method exits.
- Create a Policy
-
GenerateEntity method
We’ll focus on the generateEntity method here, because we’ll only talk about generating the entity class, not the control or service layer. Look at the code:
// Generate entity classes separately
async generateEntity() {
// Get all the table names
const tableNames: string[] = this.tableName.split(",");
await hasTableName(tableNames, async() = > {// Get table structure (source)
const structure = await getTableStructure(tableNames);
// Determine whether the instance has a base class
const { collect, base_name } = baseEntity();
// Convert the source structure to the desired structure
const columnStructure = transformStructure(structure, collect);
// Generate the entity class
generateEntity(columnStructure, this.targetPath, base_name);
});
}
Copy the code
- Get all table names, because table names are likely to be multiple and separated by commas
tableNames
; - call
hasTableName
Method to determine whether the table name was passed in, if not terminated directly; - Perform the callback if there is a table name;
What does the callback method do?
- perform
getTableStructure
Method to obtain the structure of all data tables;- call
baseEntity
Method to determine whether the instance has a base class;- call
transformStructure
Method to transform the source data into the desired structure;- call
genearteEntity
Method to generate an entity class;
getTableStructure
methods
// Get the table structure
export const getTableStructure = async (tableNames: string[]) :Promise<RowMap> => {
// @ts-ignore
const structure: Promise<RowMap> = tableNames.reduce(async (map: Promise<RowMap>, name: string) = > {const newMap = (await map);
try {
newMap[name] = await db.query(`SHOW FULL FIELDS FROM ${name}`);
} catch (error) {
throw error;
}
return map;
}, {});
return structure;
}
Copy the code
This method is very simple. It is to obtain the structure of each table through the reduce method of array, which is the basic use of Reduce and promise, which will not be described here. The obtained structure is:
{
type: [{Field: 't_binary'.Type: 'binary(10)'.Collation: null.Null: 'NO'.Key: ' '.Default: null.Extra: ' '.Privileges: 'select,insert,update,references'.Comment: ' '}, {... }].// If there are multiple table names, such as v type,sys_file, there will be one more
sys_file: [{...}, {...}]}Copy the code
baseEntity
methods
export const baseEntity = (): { base_name: string, collect: string[] } => {
let { base_name = ' ', collect = ' ' } = readYMLConfig('data_config') | | {};if(collect ! = =' '&& collect ! =null) {
collect = collect.split(', ').map((field: string) = > field.trim()).filter((field: string) = >field ! = =' ');
}
return { base_name, collect: collect === ' ' ? [] : collect };
}
Copy the code
This method is used to obtain the data_config field in the code-gen.yml configuration file.
transformStructure
Method (code more, want to see the code partners can see the source code)
The function of this method is to convert the source data structure obtained by the getTableStructure method into the option data required by the @column method, for example: source data
{
type: [{Field: 't_dec'.Type: 'a decimal (20, 8)'.Collation: null.Null: 'NO'.Key: ' '.Default: null.Extra: ' '.Privileges: 'select,insert,update,references'.Comment: ' '}}]Copy the code
Converted data
{
type: [{type: 'decimal'.length: undefined.precision: 20.scale: 8.primaryGeneratedColumn: false.enum: undefined.name: 't_dec'.collation: undefined.nullable: undefined.default: undefined.comment: undefined.update: undefined.jsType: 'number'.isIndex: false}}]Copy the code
genearteEntity
Method (code more, want to see the code partners can see the source code)
The method generates files according to the data generated by transformStructure method.
The effect
Let’s take a look at the result. Suppose we have an empty SRC directory;
├ ─ ─ the SRCCopy the code
We also have a Type data table;
When we execute v type SRC /demo or v type demo, we automatically create a demo directory containing the Entity file;
├ ─ ─ the SRC │ ├ ─ ─ demo │ │ └ ─ ─ entities │ │ └ ─ ─ the entity. The tsCopy the code
Is it so cool? Hey hey 😁
conclusion
The tool itself is simple to implement, but it can help ease a lot of development burdens and solve a lot of development problems. In fact, a lot of things look very gorgeous and complicated on the surface, but all changes are the same. Data processing is very important. I hope you can make progress together and make progress every day!
contact
Making: github.com/Veloma-Time… NPM: www.npmjs.com/package/nes… Have not understood the place can also add my wechat: __veloma__