关于后端:手摸手使用Dart语言开发后端应用来吧

28次阅读

共计 13305 个字符,预计需要花费 34 分钟才能阅读完成。

前言

这几天间断发了几篇对于 Dart 开发后端利用的文章,次要是介绍了 Dart 的一些长处,比方异步工作,并发解决,编译部署等等。

俗话说,光说不练假把式,明天咱们来真正开始一个 Dart 后端利用。

咱们要开发什么利用

假如咱们当初要开发一个社区利用,相似于 掘金 CSDN 等等,根本的性能是用户发文章,发观点。

发文章,相似于传统的 CMS 零碎

发观点,相似于当初的微博零碎

围绕外围,还有标签,分类,评论等等。

咱们用什么框架

既然打算应用 Dart 开发,有个开发框架还是有很大帮忙的。然而 Dart 的后端框架并不多,aqueduct, jaguar, DartMars 等等, 在这里,咱们应用 DartMars

源码在此 https://github.com/tangpanqin…

文档在此 https://tangpanqing.github.io…

打开文档首页,如此

嗯嗯,浓浓的 vuepress 滋味。

开始一个我的项目如此简略

依据 DartMars 的指引,在装置Dart 后,咱们能够执行以下命令来创立我的项目

# 装置 DartMars
dart 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 created
you can change dir with command: cd community
and then get dependent with command: dart pub global run dart_mars --get
and 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 dev
INFO::2021-07-03 10:14:13.601023::0::Server::Http Server has start, port=80
INFO::2021-07-03 10:14:13.608004::1::Server::Env type is dev
INFO::2021-07-03 10:14:13.624571::2::Server::Open browser and vist http://127.0.0.1:80 , you can see some info

启动胜利,通过以上信息,咱们可知:

  1. 路由配置文件曾经更新,
  2. 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());
    });
  }
}

有必要阐明一下:

RouteMetaDartMars 定义的路由元数据,相似于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.

正文完
 0