前言
这几天间断发了几篇对于 Dart
开发后端利用的文章,次要是介绍了 Dart
的一些长处,比方异步工作,并发解决,编译部署等等。
俗话说,光说不练假把式,明天咱们来真正开始一个 Dart
后端利用。
咱们要开发什么利用
假如咱们当初要开发一个社区利用,相似于掘金
,CSDN
等等,根本的性能是用户发文章,发观点。
发文章,相似于传统的CMS零碎
发观点,相似于当初的微博零碎
围绕外围,还有标签,分类,评论等等。
咱们用什么框架
既然打算应用 Dart
开发,有个开发框架还是有很大帮忙的。 然而 Dart
的后端框架并不多,aqueduct
, jaguar
, DartMars
等等, 在这里,咱们应用 DartMars
。
源码在此 https://github.com/tangpanqin...
文档在此 https://tangpanqing.github.io...
打开文档首页,如此
嗯嗯,浓浓的 vuepress
滋味。
开始一个我的项目如此简略
依据DartMars
的指引,在装置Dart
后,咱们能够执行以下命令来创立我的项目
# 装置DartMarsdart pub global activate --source git https://github.com/tangpanqing/dart_mars.git# 创立我的项目dart pub global run dart_mars --create project_name# 进入目录cd project_name# 获取依赖dart pub global run dart_mars --get # 启动我的项目dart pub global run dart_mars --serve dev
手摸手,咱们一步一步来
第一步,装置DartMars
关上命令行工具,执行
dart pub global activate --source git https://github.com/tangpanqing/dart_mars.git
感激墙的存在,我等了将近1分钟,提醒我如下:
Activated dart_mars 1.0.4 from Git repository "https://github.com/tangpanqing/dart_mars.git"
这就示意装置好了。
第二步,创立我的项目
我的项目暂定名称 community
社区,执行如下命令
dart pub global run dart_mars --create community
通过以上命令,DartMars
有了提醒
project community has been createdyou can change dir with command: cd communityand then get dependent with command: dart pub global run dart_mars --getand then start it with command: dart pub global run dart_mars --serve dev
意思说,我的项目曾经创立,接下来你须要进入目录,并且获取依赖,最初执行。
并且显示了相干命令,是不是很贴心? 谈恋爱的时候,肯定是个暖男。
第三步,进入目录
执行命令
cd community
第四步,获取依赖
执行命令
dart pub global run dart_mars --get
通过以上命令,DartMars
有了提醒
Got dependencies!
示意加载依赖实现
第五步,启动我的项目
dart pub global run dart_mars --serve dev
通过以上命令,DartMars
有了提醒
route config file has been updated, see ./lib/config/route.dart$ dart run bin\community.dart --serve devINFO::2021-07-03 10:14:13.601023::0::Server::Http Server has start, port=80INFO::2021-07-03 10:14:13.608004::1::Server::Env type is devINFO::2021-07-03 10:14:13.624571::2::Server::Open browser and vist http://127.0.0.1:80 , you can see some info
启动胜利,通过以上信息,咱们可知:
- 路由配置文件曾经更新,
- HTTP 服务曾经开始,在80端口,目前应用的是开发环境
关上浏览器,拜访 http://127.0.0.1:80 咱们就看到了经典的
hello world
循序渐进地持续编码
先看一眼我的项目构造
bin
目录是执行文件的入口
lib
目录是整个我的项目的开发目录
其余目录都是一些辅助性的,如名字所示。接下来,咱们要循序渐进的实现基本功能。
先实现第一个,用户的增查改删,并且做成规范,当前应用。
创立用户表
我曾经提前准备好了相干的sql
语句
CREATE TABLE IF NOT EXISTS `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` varchar(40) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户ID', `user_mobile` varchar(11) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户手机号', `user_password` varchar(60) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户明码', `user_nickname` varchar(60) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户昵称', `user_avatar` varchar(60) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户头像', `user_description` varchar(120) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户介绍', `create_time` bigint(20) NOT NULL DEFAULT '0' COMMENT '创立工夫', `update_time` bigint(20) NOT NULL DEFAULT '0' COMMENT '更新工夫', `delete_time` bigint(20) NOT NULL DEFAULT '0' COMMENT '删除工夫', PRIMARY KEY (`id`), UNIQUE KEY `user_id` (`user_id`), KEY `user_mobile` (`user_mobile`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表';
放到mysql
去执行
创立用户模型
用户模型用来与数据表进行对应的,不便面向对象开发。
在目录 lib/extend/model/
下,新建模型文件 User.dart
,键入如下内容
class User { int id; String userId; String userMobile; String userPassword; String userNickname; String userAvatar; String userDescription; int createTime; int updateTime; int deleteTime;}
这里只是定义了类名,以及相干属性,还须要补充一些办法。补充模型类的办法,是一个干燥的事件,倡议应用工具。
如果你应用的是 VSCode
,并且装置了 Dart Data Class Generator
插件,此时点击类名,将会呈现帮忙,点击下图红色框框内,将补充实现代码。
咱们将失去以下后果
import 'dart:convert';class User { int id; String userId; String userMobile; String userPassword; String userNickname; String userAvatar; String userDescription; int createTime; int updateTime; int deleteTime; User({ this.id, this.userId, this.userMobile, this.userPassword, this.userNickname, this.userAvatar, this.userDescription, this.createTime, this.updateTime, this.deleteTime, }); Map<String, dynamic> toMap() { return { 'id': id, 'userId': userId, 'userMobile': userMobile, 'userPassword': userPassword, 'userNickname': userNickname, 'userAvatar': userAvatar, 'userDescription': userDescription, 'createTime': createTime, 'updateTime': updateTime, 'deleteTime': deleteTime, }; } factory User.fromMap(Map<String, dynamic> map) { return User( id: map['id'], userId: map['userId'], userMobile: map['userMobile'], userPassword: map['userPassword'], userNickname: map['userNickname'], userAvatar: map['userAvatar'], userDescription: map['userDescription'], createTime: map['createTime'], updateTime: map['updateTime'], deleteTime: map['deleteTime'], ); } String toJson() => json.encode(toMap()); factory User.fromJson(String source) => User.fromMap(json.decode(source)); @override String toString() { return 'User(id: $id, userId: $userId, userMobile: $userMobile, userPassword: $userPassword, userNickname: $userNickname, userAvatar: $userAvatar, userDescription: $userDescription, createTime: $createTime, updateTime: $updateTime, deleteTime: $deleteTime)'; }}
通过方才的操作,能够看到
多了三个实例化函数 User
, User.fromMap
, User.fromJson
多了三个办法 toMap
, toJson
, toString
为什么要做这些,归根到底是因为 Dart
禁用反射,当咱们从其余中央拿到数据,无奈间接转成模型对象。只能先转成map
,或者json
字符串,而后再手工转成模型对象。
是稍稍简单了点,为了更好的性能,不算大问题。
创立服务
服务用来解决理论业务,被控制器所调用。
在目录 lib/extend/service/
下,新建服务文件 UserService.dart
,键入如下内容
import 'package:community/bootstrap/db/Db.dart';import 'package:community/bootstrap/db/DbColumn.dart';import 'package:community/bootstrap/helper/ConvertHelper.dart';import 'package:community/extend/helper/PasswordHelper.dart';import 'package:community/extend/helper/TimeHelper.dart';import 'package:community/extend/helper/UniqueHelper.dart';import 'package:community/extend/model/Page.dart';import 'package:community/extend/model/User.dart';class UserService { static String _table = "user"; /// 分页查问 static Future<Page<User>> query( List<DbColumn> condition, int pageNum, int pageSize) async { int totalCount = await Db(_table).where(condition).count('*'); List<Map<String, dynamic>> mapList = await Db(_table) .where(condition) .page(pageNum, pageSize) .order("create_time desc") .select(); List<User> list = mapList.map((e) => User.fromMap(ConvertHelper.keyToHump(e))).toList(); return Page<User>(totalCount, pageNum, pageSize, list); } /// 依据用户ID查问 static Future<User> findById(String userId) async { List<DbColumn> where = [ DbColumn.fieldToUnderLine("userId", "=", userId), DbColumn.fieldToUnderLine("deleteTime", "=", 0), ]; Map<String, dynamic> map = await Db(_table).where(where).find(); if (null == map) throw "没有找到用户"; return User.fromMap(ConvertHelper.keyToHump(map)); } /// 增加用户 static Future<User> add( String userMobile, String userPassword, String userNickname, String userAvatar, String userDescription, ) async { Map<String, dynamic> userMap = await _findByMobile(userMobile); if (null != userMap) throw '该手机号已存在'; User user = User( userId: UniqueHelper.userId(), userMobile: userMobile, userPassword: PasswordHelper.password(userPassword), createTime: TimeHelper.timestamp(), userNickname: userNickname, userAvatar: userAvatar, userDescription: userDescription, updateTime: 0, deleteTime: 0); user.id = await Db(_table).insert(ConvertHelper.keyToUnderLine(user.toMap())); return user; } /// 批改用户昵称 static Future<User> updateNickname(String userId, String userNickname) async { User user = await findById(userId); user.userNickname = userNickname; await _updateField(user.toMap(), 'userId', ['userNickname']); return user; } /// 依据用户ID删除,软删除 static Future<User> delete(String userId) async { User user = await findById(userId); user.deleteTime = TimeHelper.timestamp(); await _updateField(user.toMap(), 'userId', ['deleteTime']); return user; } /// 依据用户手机号查问 static Future<Map<String, dynamic>> _findByMobile(String userMobile) async { List<DbColumn> condition = [ DbColumn.fieldToUnderLine("userMobile", "=", userMobile), DbColumn.fieldToUnderLine("deleteTime", "=", 0), ]; Map<String, dynamic> map = await Db(_table).where(condition).find(); return map; } /// 更新表字段 static Future<int> _updateField( Map<String, dynamic> map, String keyName, List<String> fieldList) async { List<DbColumn> condition = [ DbColumn.fieldToUnderLine(keyName, '=', map[keyName]) ]; Map<String, dynamic> updateMap = {}; fieldList.forEach((fieldName) { updateMap[fieldName] = map[fieldName]; }); return await Db(_table) .where(condition) .update(ConvertHelper.keyToUnderLine(updateMap)); }}
上述代码,是对数据的增查改删,和其余语言的代码,大同小异,一些容易蛊惑的中央,略微解释下。
在分页查问中
List<User> list = mapList.map((e) => User.fromMap(ConvertHelper.keyToHump(e))).toList();
这里次要的作用是,将 mapList
这个键值对的列表,转换成 User
对象列表。
另外,因为咱们数据库的字段名是下划线格局的,而模型类的属性是驼峰格局的,所以须要一个转换过程。
ConvertHelper.keyToHump
的作用是将键名为 下划线格局
的键值对,转换成键名为 驼峰格局
的键值对。
创立控制器
控制器用于接管用户申请参数,并调用服务来解决业务,最初返回信息
在目录 lib/app/controller/
下,新建模型文件 UserController.dart
,键入如下内容
import 'package:community/bootstrap/Context.dart';import 'package:community/bootstrap/db/DbColumn.dart';import 'package:community/bootstrap/db/DbTrans.dart';import 'package:community/bootstrap/helper/VerifyHelper.dart';import 'package:community/bootstrap/meta/RouteMeta.dart';import 'package:community/extend/model/Page.dart';import 'package:community/extend/model/User.dart';import 'package:community/extend/service/UserService.dart';class UserController { @RouteMeta('/home/user/query', 'GET|POST') static void query(Context ctx) async { int pageNum = ctx.getPositiveInt('pageNum', def: 1); int pageSize = ctx.getPositiveInt('pageSize', def: 20); await DbTrans.simple(ctx, () async { List<DbColumn> condition = []; Page<User> res = await UserService.query(condition, pageNum, pageSize); ctx.showSuccess('已获取', res.toMap()); }); } @RouteMeta('/home/user/findById', 'GET|POST') static void findById(Context ctx) async { String userId = ctx.getString('userId'); if (VerifyHelper.empty(userId)) return ctx.showError('用户ID不能为空'); await DbTrans.simple(ctx, () async { User res = await UserService.findById(userId); ctx.showSuccess('已获取', res.toMap()); }); } @RouteMeta('/home/user/add', 'GET|POST') static void add(Context ctx) async { String userMobile = ctx.getString('userMobile'); String userPassword = ctx.getString('userPassword'); String userNickname = ctx.getString('userNickname'); String userAvatar = ctx.getString('userAvatar'); String userDescription = ctx.getString('userDescription'); if (VerifyHelper.empty(userMobile)) return ctx.showError('用户手机号不能为空'); if (VerifyHelper.empty(userPassword)) return ctx.showError('用户明码不能为空'); if (VerifyHelper.empty(userNickname)) return ctx.showError('用户昵称不能为空'); if (VerifyHelper.empty(userAvatar)) return ctx.showError('用户头像不能为空'); if (VerifyHelper.empty(userDescription)) return ctx.showError('用户形容不能为空'); await DbTrans.simple(ctx, () async { User res = await UserService.add( userMobile, userPassword, userNickname, userAvatar, userDescription); ctx.showSuccess('已增加', res.toMap()); }); } @RouteMeta('/home/user/updateNickname', 'GET|POST') static void updateNickname(Context ctx) async { String userId = ctx.getString('userId'); String userNickname = ctx.getString('userNickname'); if (VerifyHelper.empty(userId)) return ctx.showError('用户ID不能为空'); if (VerifyHelper.empty(userNickname)) return ctx.showError('用户昵称不能为空'); await DbTrans.simple(ctx, () async { User res = await UserService.updateNickname(userId, userNickname); ctx.showSuccess('已更改', res.toMap()); }); } @RouteMeta('/home/user/delete', 'GET|POST') static void delete(Context ctx) async { String userId = ctx.getString('userId'); if (VerifyHelper.empty(userId)) return ctx.showError('用户ID不能为空'); await DbTrans.simple(ctx, () async { User res = await UserService.delete(userId); ctx.showSuccess('已删除', res.toMap()); }); }}
有必要阐明一下:
RouteMeta
是 DartMars
定义的路由元数据,相似于java
里的注解。
雷同的作用是,能够对代码进行形容,让开发者晓得所形容的代码的性能。
不同的是,因为 DartMars
没有反射,所以程序不能在运行的时候获取元数据或者说注解的信息,也就无奈实现相似于java
里注解生成代码的性能。
当然,既然运行的时候不能生成代码,咱们另寻他图,在编译之前生成即可。
自动更新路由配置
接下来,咱们启动我的项目,执行如下命令:
dart pub global run dart_mars --serve dev
请留神,控制台打印的有这样一句话
route config file has been updated, see ./lib/config/route.dart
说路由配置文件曾经更新,地址是 ./lib/config/route.dart
,咱们看看去
import '../bootstrap/helper/RouteHelper.dart';import '../app/controller/HomeController.dart' as app_controller_HomeController;import '../app/controller/UserController.dart' as app_controller_UserController;/// /// don't modify this file yourself, this file content will be replace by DartMars/// /// for more infomation, see doc about Route /// /// last replace time 2021-07-03 14:53:51.588722 /// void configRoute(){ RouteHelper.add('GET', '/', app_controller_HomeController.HomeController.index); RouteHelper.add('GET', '/user', app_controller_HomeController.HomeController.user); RouteHelper.add('GET', '/city/:cityName', app_controller_HomeController.HomeController.city); RouteHelper.add('GET|POST', '/home/user/query', app_controller_UserController.UserController.query); RouteHelper.add('GET|POST', '/home/user/findById', app_controller_UserController.UserController.findById); RouteHelper.add('GET|POST', '/home/user/add', app_controller_UserController.UserController.add); RouteHelper.add('GET|POST', '/home/user/updateNickname', app_controller_UserController.UserController.updateNickname); RouteHelper.add('GET|POST', '/home/user/delete', app_controller_UserController.UserController.delete);}
果然,最初面增加了 5
个路由规定,和咱们方才在 UserController
里定义的一样。
另外,如文件所提醒的,这个文件不要手动更改,当你运行 --serve
命令时, DartMars
会自动更新。
测试接口
测试接口的工作非常简单了,能够应用业余工具,也能够在浏览器中间接来。文章篇幅无限,我就测试 2
个,其余的接口,有趣味的同学本人来。
测试增加用户接口
http://127.0.0.1/home/user/add?userMobile=18512345679&userPassword=123456&userNickname=tang&userAvatar=http://www.test.com/1.jpg&userDescription=test
返回如下
{ "code": 200, "msg": "已增加", "data": { "id": 2, "userId": "1625295731292004882", "userMobile": "18512345679", "userPassword": "4616221982a9d1759d1d0cec7249a6d71da960d3", "userNickname": "tang", "userAvatar": "http://www.test.com/1.jpg", "userDescription": "test", "createTime": 1625295731, "updateTime": 0, "deleteTime": 0 }}
一切正常,十分棒。
测试查问单个用户接口
http://127.0.0.1/home/user/findById?userId=1625295731292004882
返回如下
{ "code": 200, "msg": "已获取", "data": { "id": 2, "userId": "1625295731292004882", "userMobile": "18512345679", "userPassword": "4616221982a9d1759d1d0cec7249a6d71da960d3", "userNickname": "tang", "userAvatar": "http://www.test.com/1.jpg", "userDescription": "test", "createTime": 1625295731, "updateTime": 0, "deleteTime": 0 }}
一切正常,十分棒。
总结
可能看到这里的同学,想必都是真爱了。
由上述流程走下来,能够看出,用 Dart
开发后端利用,与其余语言开发,并无太大的区别。也阐明一个事件,其余语言的开发者,想转用 Dart
开发后端应用程序,是一件很容易的事件。
加之 Dart
在客户端开发畛域的胜利, 一种语言实现客户端与服务端相对不再是幻想。
That's All, Enjoy.