您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

基于NodeJS的KOA2框架实现restful API网站后台

时间:2021-07-27 11:37:34  来源:  作者:程序你好

在此前写的文章“从零基础入门进行小程序开发实战”中,已经介绍过背单词的小程序,因为没有备案的服务器资源只能使用系统后台提供的缓存功能存储用户数据。缓存有大小限制,而且只提供key-value的存储方式,使用起来也很不方便。

最近域名和服务器已经申请下来,网站备案也在进行中,准备自己搭建数据库服务器和开发一套实现restful api的后台代码。关于技术栈的选择也颇花费了一些功夫,传统的技术路线JAVA和.net core都能提供相关的成熟的框架,我本人技术背景对这方面也很熟悉,可是既然是自己兴趣又不是公司的项目,当然还是想要尝试一下新的不一样的技术实现。

研究了Python的Flask框架和基于nodejs的koa2框架,都是大名鼎鼎,可之前接触不多,最后选择了koa2框架,写小程序的后台,顺便也学习一下这方面的开发。

介绍

koa2是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架。 使用 koa 编写 web 应用,可以免除重复繁琐的回调函数嵌套, 并极大地提升错误处理的效率。koa 不在内核方法中绑定任何中间件, 它仅仅提供了一个轻量优雅的函数库,使得编写 Web 应用变得得心应手。

node基础知识介绍

在开始项目前,先简单介绍下node相关的一些基础知识,通过npm init初始化一个node项目时,会生成一个package.json的配置文件,包括项目名称、版本、作者、依赖等相关信息,主要说一下其中的bin字段。
很多包都有一个或多个可执行的文件,希望放在PATH中,(实际上,就是这个功能让npm可执行的)。
当你要用这个功能时,需要给package.json中的bin字段添加一个命令名,并指向需要执行的文件(即后文的入口文件)。初始化的时候npm会将他链接到prefix/bin(全局初始化)或者./node_modules/.bin/(本地初始化)。

开发环境搭建和脚手架安装

如果还没有nodejs和npm,首先需要安装这两个软件,注意最低版本要求。

脚手架对于前端程序员并不陌生,像vue-cli,react-native-cli等,全局安装后,只需要在命令行中敲入一个简单的命令,便可帮我们快速的生成一个初始项目,如vue init webpack projectName,即可生成一个初始的vue项目。

安装Koa2脚手架非常简单:

先执行下面命令:
npm install -g koa-generator

使用koa-generator生成koa2项目

在你的工作目录下,输入:

$ koa2 HelloKoa2

成功创建项目后,进入项目目录(安装项目依赖) :

npm install

启动运行项目:

$ npm start

项目启动后,默认端口号是3000,在浏览器中运行就能看到页面。

 

项目结构介绍

├─src           应用目录(可设置)
│  ├─controller                 控制器
│  ├─config                      配置文件
│  ├─db                           数据库相关配置
    ├─ model                     各个数据模型
    ├─ index.js                   数据库配置页面
│  ├─middleWares           中间件
│  ├─routes                      路由
    ├─ index.js                   路由入口文件
    ├─ users.js                  用户路由
│  ├─service                    服务
│  ├─App.js                   入口文件
基于NodeJS的KOA2框架实现restful API网站后台

 

package.json文件

每个Nodejs项目的根目录下面,一般都会有一个package.json文件。该文件可以由npm init生成,定义了项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)。 package.json文件内部就是一个JSON对象,该对象的每一个成员就是当前项目的一项设置。

 

连接MySQL数据库

mysql模块是node操作MySQL的引擎,可以在node.js环境下对MySQL数据库进行建表,增、删、改、查等操作。

  "dependencies": {
    "glob": "^7.1.3",
    "jsonwebtoken": "^8.4.0",
    "koa": "^2.6.2",
    "koa-body": "^4.0.4",
    "koa-bodyparser": "^4.2.1",
    "koa-cors": "0.0.16",
    "koa-jwt": "^3.5.1",
    "koa-router": "^7.4.0",
    "koa-session": "^5.10.0",
    "koa-static": "^5.0.0",
    "mysql": "^2.16.0"
  }

在package.json依赖中设置"mysql": "^2.16.0"

const config = require('../config').database
const mysql = require('mysql')
const pool = mysql.createPool(config)
const query = function(sql,succCb,errCb){
  pool.getConnection(function(err,conn){
    if (err) {
      let data = {
        code:500,
        message:"请求失败",
        data:err
      };
      errCb(data);
    }else {
      conn.query(sql,function(err,result){
        if (err) {
          let data = {
            code:500,
            message:"请求失败",
            data:err
          };
          errCb(data);
        }else {
          succCb(result);
          conn.release();
        }
      })
    }
  })
}
module.exports = query;
const query = require('./query')
const Tools = require('./tools')
const mysql = require('mysql')
const config = require('../config').database
const pool = mysql.createPool(config)
const getByPage = function(tb,page,limit){
  return new Promise((resolve,reject)=>{
    let start = (page-1)*limit;
    let command = `select * from ${tb} limit ${start},${limit}`;
    query(command,function(res){
      let data = {
        code:200,
        message:'获取成功',
        data:{
          list:res,
          pagination:{
            size:res.length,
            currentPage:parseInt(page)
          }
        }
      }
      query(`select count(*) from ${tb}`,function(res){
        data.data.pagination['total'] = res[0]["count(*)"];
        data.data.pagination['totalPage'] = parseInt(res[0]["count(*)"]/limit) + ((res[0]["count(*)"]%limit)>0?1:0);
        resolve(data);
      },function(err){
        resolve(data)
      })
    },function(err){
      resolve(err);
    })
  })
}
const getForeignInfo = function(tb,filter,foreign){//主表,筛选条件,外键信息
  let queryStr = '';//查询条件
  for (let key in filter) {
    queryStr += `${tb}.${key}=${filter[key]}&`;
  }
  queryStr = queryStr.substr(0,queryStr.length-1);
  let as = '';
  let join = '';
  let tables = ` from ${tb} ${tb}`;
  for (let key1 in foreign) {
    let table = foreign[key1].table;
    let data = foreign[key1].data;
    let key = key1;
    join += ` join ${table} ${table} on ${tb}.${key}=${table}.id `;
    for(let key2 in data){
      as += `,${table}.${key2} as ${data[key2]}`
    }
  }
  let str = `select ${tb}.*`+as+tables+join+(queryStr==''?'':'where '+queryStr);
  console.log(str);
  return str;
}
const Sql = {
  queryAll:function(tb,filter,foreign){  //获取表的全部记录
    if (filter && !Tools.isEmptyObject(filter)) { //分页
      return getByPage(tb,filter.page,filter.limit,foreign)
    }else {  //全部
      return new Promise((resolve,reject)=>{
        let str = `select * from ${tb}`;
        if (foreign) {
          str = getForeignInfo(tb,filter,foreign);
        }
        query(str,function(res){
          let data = {
            code:200,
            message:'获取成功',
            data:{
              list:res,
              size:res.length
            }
          }
          resolve(data);
        },function(err){
          resolve(err);
        })
      })
    }
  },
  query:function(tb,id,foreign){ //根据id获取
    return new Promise((resolve,reject)=>{
      query(`select * from ${tb} where id=${id}`,function(res){
        let data = {
          code:200,
          message:res.length==0?'查无数据':'获取成功',
          data:res.length==0?{}:res[0]
        }
        resolve(data);
      },function(err){
        resolve(err);
      })
    })
  },
  queryByField:function(tb,fieldName,fieldValue){ //根据field获取
    return new Promise((resolve,reject)=>{
      query(`select * from ${tb} where ${fieldName}="${fieldValue}"`,function(res){
        let data = {
          code:200,
          message:res.length==0?'查无数据':'获取成功',
          data:res.length==0?{}:res[0]
        }
        resolve(data);
      },function(err){
        resolve(err);
      })
    })
  },
  insert:function(tb,data){  //插入一条记录
    return new Promise((resolve,reject)=>{
      let [keys,values] = [[],[]];
      for (let key in data) {
        if (data.hasOwnProperty(key)) {
          keys.push(key);
          if (Object.prototype.toString.call(data[key]) == '[object String]') {
            values.push(`"${data[key]}"`)
          }else {
            values.push(data[key])
          }
        }
      }
      query(`insert into ${tb} (${keys}) values (${values})`,function(res){
        let id = res.insertId;
        let data = {
          code:200,
          message:'添加成功',
          data:res
        }
        query(`select * from ${tb} where id=${id}`,function(res){
          data.data = res[0];
          resolve(data);
        },function(err){
          resolve(data);
        })
      },function(err){
        resolve(err);
      })
    })
  },
  insertRows:function(tb,arr){ //插入多条记录
    return new Promise((resolve,reject)=>{
      let [keys,values] = [[],[]];
      for (let i = 0; i < arr.length; i++) {
        let [data,value] = [arr[i],[]];
        for (let key in data) {
          if (data.hasOwnProperty(key)) {
            if (i==0) {
              keys.push(key);
            }
            if (Object.prototype.toString.call(data[key]) == '[object String]') {
              value.push(`"${data[key]}"`)
            }else {
              value.push(data[key])
            }
          }
        }
        values.push(`(${value})`);
      }
      query(`insert into ${tb} (${keys}) values ${values}`,function(res){
        let data = {
          code:200,
          message:'添加成功',
          data:res
        }
        let ids = [];
        for (let i = 0; i < res.affectedRows; i++) {
          ids.push(res.insertId+i);
        }
        query(`select * from ${tb} where id in (${ids})`,function(res){
          data.data = {
              list:res,
              size:res.length
          };
          resolve(data);
        },function(err){
          resolve(data);
        })
      },function(err){
        resolve(err);
      })
    })
  },
  update:function(tb,id,data){ //根据id修改单条记录
    return new Promise((resolve,reject)=>{
      let [str,index] = ['',0];
      for (let key in data) {
        if (data.hasOwnProperty(key)) {
          if (index!=0) {
            str += ','
          }
          if (Object.prototype.toString.call(data[key]) == '[object String]'){
            str += `${key}="${data[key]}"`
          }
          else {
            str += `${key}=${data[key]}`
          }
          index++;
        }
      }
      query(`update ${tb} set ${str} where id=${id}`,function(res){
        let data = {
          code:200,
          message:'修改成功',
          data:res
        }
        query(`select * from ${tb} where id=${id}`,function(res){
          data.data = res[0];
          resolve(data);
        },function(err){
          resolve(data);
        })
      },function(err){
        resolve(err);
      })
    })
  },
  updateRows:function(tb,arr){  //修改多条记录
    return new Promise((resolve,reject)=>{
      let [str,ids,len,keys] = ['',[],arr.length,Object.keys(arr[0])];
      for (let x = 0; x < len; x++) {
        ids.push(arr[x].id);
      }
      for (let i = 0; i < keys.length; i++) {
        let k = keys[i];
        if (k!='id') {
          str += `${k} = case id `;
          for (let j = 0; j < len; j++) {
            str += `when ${arr[j].id} then `;
            if (Object.prototype.toString.call(arr[j][k]) == '[object String]'){
              str += `"${arr[j][k]}" `
            }
            else{
              str += `${arr[j][k]} `
            }
          }
          str += 'end'
          if (i<keys.length-1) {
            str += ','
          }
        }
      }
      query(`update ${tb} set ${str} where id in (${ids})`,function(res){
        let data = {
          code:200,
          message:'修改成功',
          data:res
        }
        query(`select * from ${tb} where id in (${ids})`,function(res){
          data.data = {
              list:res,
              size:res.length
          };
          resolve(data);
        },function(err){
          resolve(data);
        })
      },function(err){
        resolve(err);
      })
    })
  },
  delete:function(tb,id){ //根据id删除单条记录
    return new Promise((resolve,reject)=>{
      query(`delete from ${tb} where id=${id}`,function(res){
        let data = {
          code:200,
          message:'删除成功',
          data:res
        }
        resolve(data);
      },function(err){
        resolve(err);
      })
    })
  },
  deleteRows:function(tb,data){ //根据id数组删除多条记录
    return new Promise((resolve,reject)=>{
      query(`delete from ${tb} where id in (${data})`,function(res){
        let data = {
          code:200,
          message:'删除成功',
          data:res
        }
        resolve(data);
      },function(err){
        resolve(err);
      })
    })
  },
  search:function(tb,data,foreign){ //根据条件准确查询
    let queryStr = '';//查询条件
    for (let key in data) {
      queryStr += `${key}=${data[key]}&`;
    }
    queryStr = queryStr.substr(0,queryStr.length-1);
    let str;
    if (foreign) {
      str = getForeignInfo(tb,data,foreign);
    }else {
      str = `select * from ${tb} where ${queryStr}`
    }
    return new Promise((resolve,reject)=>{
      query(str,function(res){
        resolve({
          code:200,
          message:'获取成功',
          data:res
        });
      },function(err){
        resolve(err);
      });
    })
  },
  searchVague:function(tb,val,fields,foreign){//根据条件模糊查询
    let str = `select * from ${tb} where concat(`;
    for (let i = 0; i < fields.length; i++) {
      str += `${fields[i]},`;
    }
    str = str.substring(0,str.length-1);
    str += `) like %${val}%`;
    if (fields.length==1){
      str = `select * from ${tb} where ${fields[0]} like '%${val}%'`;
    }
    return new Promise((resolve,reject)=>{
      query(str,function(res){
        resolve({
          code:200,
          message:'获取成功',
          data:res
        });
      },function(err){
        resolve(err);
      });
    })
  }
}

module.exports = Sql;

 

登录和JWT认证实现

引入 "jsonwebtoken": "^8.4.0",模块

const jwt = require('jsonwebtoken');
const Token = {
  encrypt:function(data,time){ //data加密数据,time过期时间
    return jwt.sign(data, 'token', {expiresIn:time})
  },
  decrypt:function(token){
    try {
      let data = jwt.verify(token, 'token');
      return {
        token:true,
        id:data.id
      };
    } catch (e) {
      return {
        token:false,
        data:e
      }
    }
  }
}
module.exports = Token

登录成功返回token代码:

token = jwt.sign({id:res[0].id}, 'token', {expiresIn: '15d'})

restful api实现示例

需求:小程序用户输入单词,返回这个单词的详细介绍,包含中文释义和常用例句。

建立wordDesc表

基于NodeJS的KOA2框架实现restful API网站后台

 

添加一个routers文件

wordDesc.js

const router = require('koa-router')();   //路由
const Sql = require('../utils/sql');
const query = require('../utils/query');
const Tools = require('../utils/tools');
const jwt = require('jsonwebtoken');
const Token = require('../utils/token')
const tbName = 'worddesc';
const preUrl = '/api/worddesc';
let codeList = {};

api/worddesc/test

.get(`${preUrl}/:word`,async(ctx,next)=>{ //获取word desc
   let word = ctx.params.word;
   let data = Token.decrypt(ctx.header.authorization);
   if (data.token) {
     let res = await Sql.queryByField(tbName,"word", word);
     ctx.body = res;
   }else {
     ctx.body = {
       code:401,
       message:'failed',
       data:data
     };
   }
 })
{
    "code": 200,
    "message": "获取成功",
    "data": {
        "id": 2,
        "word": "test",
        "description": "this is a test.",
        "bookid": "2",
        "createdtime": null,
        "lastupdatetime": null,
        "maxl": 2
    }

分页获取列表数据

/api/worddesc?page=2&count=2

.get(`${preUrl}`,async(ctx,next)=>{ //获取word desc

   let data = Token.decrypt(ctx.header.authorization);
   if (data.token) {

     let page = ctx.query.page;
     if(page == undefined){
         page = 1;
     }
     let count = ctx.query.count;

     if(count ==undefined){
         count = 500;
     }

     let filter = {
         page:page,
         limit:count
     }
     

     let res = await Sql.queryAll(tbName,filter);
     ctx.body = res;
   }else {
     ctx.body = {
       code:401,
       message:'failed',
       data:data
     };
   }
 })
{
    "code": 200,
    "message": "获取成功",
    "data": {
        "list": [
            {
                "id": 2,
                "word": "test",
                "description": "this is a test.",
                "bookid": "2",
                "createdtime": null,
                "lastupdatetime": null,
                "maxl": 2
            },
            {
                "id": 3,
                "word": "year3",
                "description": "year3",
                "bookid": "3",
                "createdtime": null,
                "lastupdatetime": null,
                "maxl": 3
            },
            {
                "id": 4,
                "word": "ljtest",
                "description": "desc........",
                "bookid": "2",
                "createdtime": null,
                "lastupdatetime": null,
                "maxl": null
            },
            {
                "id": 6,
                "word": "lj2",
                "description": null,
                "bookid": null,
                "createdtime": null,
                "lastupdatetime": null,
                "maxl": null
            },
            {
                "id": 7,
                "word": "lj3",
                "description": null,
                "bookid": null,
                "createdtime": null,
                "lastupdatetime": null,
                "maxl": null
            },
            {
                "id": 8,
                "word": "lj4",
                "description": null,
                "bookid": null,
                "createdtime": null,
                "lastupdatetime": null,
                "maxl": null
            }
        ],
        "pagination": {
            "size": 6,
            "currentPage": 1,
            "total": 6,
            "totalPage": 1
        }
    }
}

同样增加添加、修改、删除api

 .post(`${preUrl}`,async(ctx,next)=>{ //添加信息
   let data = Token.decrypt(ctx.header.authorization);
   
   let word = ctx.request.body.word;

   if (data.token) {
     let wordRes = await  Sql.queryByField(tbName,"word",word);
     console.log("------------");
     console.log(wordRes);
     if(wordRes.data.id != undefined)
     {
        let res = await Sql.update(tbName,wordRes.data.id,ctx.request.body);
        ctx.body = res;
     }else{
        
        let res = await Sql.insert(tbName,ctx.request.body);
        ctx.body = res;
     }
     
     
    
   }else {
     ctx.body = {
       code:401,
       message:'failed',
       data:data
     };
   }
  })

  .delete(`${preUrl}/:word`,async(ctx,next)=>{ //修改信息
    let data = Token.decrypt(ctx.header.authorization);
    
    let word = ctx.params.word;

    if (data.token) {
        let wordRes = await  Sql.queryByField(tbName,"word",word);
        if(wordRes.data.id != undefined)
        {

            let res = await Sql.delete(tbName,wordRes.data.id );
            ctx.body = res;
        }else{

        ctx.body = {
        code:200,
        message:'no record',
        data:''
        };
    }
        
        
        
    }else {
        ctx.body = {
        code:401,
        message:'failed',
        data:data
        };
    }

   })

  .put(`${preUrl}/:word`,async(ctx,next)=>{ //修改信息
   let data = Token.decrypt(ctx.header.authorization);
   
   let word = ctx.params.word;
   console.log(word);

   if (data.token) {
     let wordRes = await  Sql.queryByField(tbName,"word",word);
     console.log("------------");
     console.log(wordRes);
     if(wordRes.data.id != undefined)
     {
        console.log("id------------");
        console.log(wordRes.data.id);
        console.log(ctx.request.body);
        let res = await Sql.update(tbName,wordRes.data.id ,ctx.request.body);
        ctx.body = res;
     }else{

       ctx.body = {
       code:200,
       message:'no record',
       data:''
     };
     }
     
     
    
   }else {
     ctx.body = {
       code:401,
       message:'failed',
       data:data
     };
   }
 })

管理和发布api

开发过程中命令行输入 node app.js 可以打开命令窗口启动运行,窗口中显示调试或错误信息,关闭窗口则结束进程。

生产环境中可以使用pm2来启动进程,M2是可以用于生产环境的Nodejs的进程管理工具,并且它内置一个负载均衡。它不仅可以保证服务不会中断一直在线,并且提供0秒reload功能,还有其他一系列进程管理、监控功能。并且使用起来非常简单。

安装pm2

npm install -g pm2

下面列出常用命令

$ npm install pm2 -g     # 命令行安装 pm2 
$ pm2 start app.js -i 4 #后台运行pm2,启动4个app.js 
                                # 也可以把'max' 参数传递给 start
                                # 正确的进程数目依赖于Cpu的核心数目
$ pm2 start app.js --name my-api # 命名进程
$ pm2 list               # 显示所有进程状态
$ pm2 monit              # 监视所有进程
$ pm2 logs               #  显示所有进程日志
$ pm2 stop all           # 停止所有进程
$ pm2 restart all        # 重启所有进程
$ pm2 reload all         # 0秒停机重载进程 (用于 NETWORKED 进程)
$ pm2 stop 0             # 停止指定的进程
$ pm2 restart 0          # 重启指定的进程
$ pm2 startup            # 产生 init 脚本 保持进程活着
$ pm2 web                # 运行健壮的 computer API endpoint (http://localhost:9615)
$ pm2 delete 0           # 杀死指定的进程
$ pm2 delete all         # 杀死全部进程

 

总结

对于nodejs能够流行起来一点都不感到意外,开发起来太简单和方便了。跟java这些传统的技术相比,写nodejs脚本甚至感觉不像是在编程,真的像玩一样,极大了拉低了程序员的门槛。

还有一个事实就是JavaScript是Web开发者们熟知的语言,大部分人都了解JavaScript或多少使用过它。所以说,从其他技术转型到Node.js是很简单的。

跟java、.net这些传统的技术路线相比,nodejs项目在安装、调试、部署和发布都很方便,很多Web服务器和云服务提供商都支持Node.js的Web应用。



Tags:KOA2框架   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
在此前写的文章“从零基础入门进行小程序开发实战”中,已经介绍过背单词的小程序,因为没有备案的服务器资源只能使用系统后台提供的缓存功能存储用户数据。缓存有大小限制,而且...【详细内容】
2021-07-27  Tags: KOA2框架  点击:(108)  评论:(0)  加入收藏
▌简易百科推荐
近日只是为了想尽办法为 Flask 实现 Swagger UI 文档功能,基本上要让 Flask 配合 Flasgger, 所以写了篇 Flask 应用集成 Swagger UI 。然而不断的 Google 过程中偶然间发现了...【详细内容】
2021-12-23  Python阿杰    Tags:FastAPI   点击:(6)  评论:(0)  加入收藏
文章目录1、Quartz1.1 引入依赖<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version></dependency>...【详细内容】
2021-12-22  java老人头    Tags:框架   点击:(11)  评论:(0)  加入收藏
今天来梳理下 Spring 的整体脉络啦,为后面的文章做个铺垫~后面几篇文章应该会讲讲这些内容啦 Spring AOP 插件 (了好久都忘了 ) 分享下 4ye 在项目中利用 AOP + MybatisPlus 对...【详细内容】
2021-12-07  Java4ye    Tags:Spring   点击:(14)  评论:(0)  加入收藏
&emsp;前面通过入门案例介绍,我们发现在SpringSecurity中如果我们没有使用自定义的登录界面,那么SpringSecurity会给我们提供一个系统登录界面。但真实项目中我们一般都会使用...【详细内容】
2021-12-06  波哥带你学Java    Tags:SpringSecurity   点击:(18)  评论:(0)  加入收藏
React 简介 React 基本使用<div id="test"></div><script type="text/javascript" src="../js/react.development.js"></script><script type="text/javascript" src="../js...【详细内容】
2021-11-30  清闲的帆船先生    Tags:框架   点击:(19)  评论:(0)  加入收藏
流水线(Pipeline)是把一个重复的过程分解为若干个子过程,使每个子过程与其他子过程并行进行的技术。本文主要介绍了诞生于云原生时代的流水线框架 Argo。 什么是流水线?在计算机...【详细内容】
2021-11-30  叼着猫的鱼    Tags:框架   点击:(21)  评论:(0)  加入收藏
TKinterThinter 是标准的python包,你可以在linx,macos,windows上使用它,你不需要安装它,因为它是python自带的扩展包。 它采用TCL的控制接口,你可以非常方便地写出图形界面,如...【详细内容】
2021-11-30    梦回故里归来  Tags:框架   点击:(26)  评论:(0)  加入收藏
前言项目中的配置文件会有密码的存在,例如数据库的密码、邮箱的密码、FTP的密码等。配置的密码以明文的方式暴露,并不是一种安全的方式,特别是大型项目的生产环境中,因为配置文...【详细内容】
2021-11-17  充满元气的java爱好者  博客园  Tags:SpringBoot   点击:(25)  评论:(0)  加入收藏
一、搭建环境1、创建数据库表和表结构create table account(id INT identity(1,1) primary key,name varchar(20),[money] DECIMAL2、创建maven的工程SSM,在pom.xml文件引入...【详细内容】
2021-11-11  AT小白在线中  搜狐号  Tags:开发框架   点击:(29)  评论:(0)  加入收藏
SpringBoot开发的物联网通信平台系统项目功能模块 功能 说明 MQTT 1.SSL支持 2.集群化部署时暂不支持retain&will类型消 UDP ...【详细内容】
2021-11-05  小程序建站    Tags:SpringBoot   点击:(55)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条