1
头图

Preface

In the past few days, I have published several articles about Dart developing back-end applications, mainly introducing Dart , such as asynchronous tasks, concurrent processing, compilation and deployment, and so on.

As the saying goes, if you just Dart fakes, let’s start a 060e80910864a8 back-end application today.

What application do we want to develop

Suppose we now want to develop a community application, similar to Nuggets, CSDN etc. The basic function is for users to post articles and post opinions.

Post articles, similar to traditional CMS system

Send opinions, similar to the current Weibo system

Around the core, there are tags, categories, comments, and so on.

What framework do we use

Since we plan to use Dart development, a development framework is still very helpful. However, Dart are not many back-end frameworks for aqueduct , jaguar , DartMars , 060e8091086536, etc., here, we use DartMars .

The source code is here https://github.com/tangpanqing/dart_mars

The document is here https://tangpanqing.github.io/dart_mars_docs/zh/

Open the document homepage, like this

微信图片_20210703095222.png

vuepress , the strong flavor of 060e809108659e.

Starting a project is so easy

According to the guidelines of DartMars Dart , we can execute the following command to create the project

# 安装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

Touch your hands, let's take it step by step

The first step is to install DartMars

Open the command line tool and execute

dart pub global activate --source git https://github.com/tangpanqing/dart_mars.git

Thanks for the existence of the wall, I waited for nearly 1 minute and prompted me as follows:

Activated dart_mars 1.0.4 from Git repository "https://github.com/tangpanqing/dart_mars.git"

This means that the installation is complete.

The second step is to create the project

The tentative name of the project is community community, and the following orders are executed

dart pub global run dart_mars --create community

After the above command, DartMars a prompt

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

It means that the project has been created, then you need to enter the directory, get the dependencies, and finally execute it.

And the related commands are displayed, isn't it very considerate? When you are in love, you must be a warm man.

The third step is to enter the directory

Excuting an order

cd community

The fourth step is to obtain dependencies

Excuting an order

dart pub global run dart_mars --get

After the above command, DartMars a prompt

Got dependencies!

Indicates that the loading dependency is complete

The fifth step, start the project

dart pub global run dart_mars --serve dev

After the above command, DartMars a prompt

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

The startup is successful. From the above information, we can know:

  1. The routing configuration file has been updated,
  2. HTTP service has started, on port 80, currently using the development environment

Open the browser, visit http://127.0.0.1:80 we can see the classic

hello world

Continue coding step by step

Take a look at the project structure first

微信图片_20210703115930.png

bin directory is the entrance to the executable file

lib directory is the development directory of the entire project

Other directories are auxiliary, as the name suggests. Next, we have to complete the basic functions step by step.

First complete the first one, add, check, modify, delete, and make a standard for later use.

Create user table

I have prepared the relevant sql statement in advance

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 to 060e8091086871 to execute

Create user model

The user model is used to correspond to the data table to facilitate object-oriented development.
In the directory lib/extend/model/ , create a new model file User.dart , and type the following

class User {
  int id;
  String userId;
  String userMobile;
  String userPassword;
  String userNickname;
  String userAvatar;
  String userDescription;
  int createTime;
  int updateTime;
  int deleteTime;
}

Only the class name and related attributes are defined here, and some methods need to be added. The method of supplementing model classes is a boring thing, and it is recommended to use tools.

If you are using VSCode and installed the Dart Data Class Generator plug-in, click the class name at this time, and the help will appear. Click inside the red box in the figure below to complete the code.

微信图片_20210703120712.png

We will get the following result

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)';
  }
}

After the operation just now, you can see

Three more instantiated functions User , User.fromMap , User.fromJson

Three more methods toMap , toJson , toString

Why do this, in the final analysis, is because Dart disables reflection. When we get data from other places, it cannot be directly converted into model objects. It can only be converted into a map or json string first, and then manually converted into a model object.

It's a little more complicated. For better performance, it's not a big problem.

Create service

The service is used to process the actual business and is called by the controller.

In the directory lib/extend/service/ , create a new service file UserService.dart , type the following

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));
  }
}

The above code is the addition, modification, and deletion of data. It is similar to the code in other languages. Some places that are easy to confuse, please explain a little bit.

In pagination query

List<User> list =
        mapList.map((e) => User.fromMap(ConvertHelper.keyToHump(e))).toList();

The main role here is to mapList this list of key-value pairs, converted into User list of objects.

In addition, because the field names of our database are in underscore format, and the attributes of the model class are in camel case, a conversion process is required.

ConvertHelper.keyToHump is to convert the key- underlined format into the key-value pair with the key name camel case format.

Create controller

The controller is used to receive user request parameters, call the service to process the business, and finally return information

In the directory lib/app/controller/ , create a new model file UserController.dart , and type the following

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());
    });
  }
}

It is necessary to explain:

RouteMeta is the routing metadata defined by DartMars , similar to the annotations in java

The same effect is that the code can be described, so that the developer knows the function of the code described.

The difference is that because DartMars does not have reflection, the program cannot obtain metadata or annotated information when it is running, and it cannot complete the function similar to the annotation generated code in java

Of course, since the code cannot be generated when it is running, we can find another diagram and generate it before compiling.

Automatically update routing configuration

Next, we start the project and execute the following commands:

dart pub global run dart_mars --serve dev

Please note that there is a sentence printed on the console

route config file has been updated, see ./lib/config/route.dart

Said that the routing configuration file has been updated, the address is ./lib/config/route.dart , let’s take a look

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);
}

Sure enough, the 5 routing rule was UserController , which is the same as the one we just defined in 060e8091086df0.

In addition, as the file prompts, do not manually change this file. When you run the --serve command, DartMars will be updated automatically.

Test interface

The work of the test interface is very simple. You can use professional tools or directly in the browser. The length of the article is limited, so I will test 2 , other interfaces, interested students come by themselves.

Test add user interface

http://127.0.0.1/home/user/add?userMobile=18512345679&userPassword=123456&userNickname=tang&userAvatar=http://www.test.com/1.jpg&userDescription=test

Returns as follows

{
  "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
  }
}

Everything is normal and great.

Test and query a single user interface

http://127.0.0.1/home/user/findById?userId=1625295731292004882

Returns as follows

{
  "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
  }
}

Everything is normal and great.

to sum up

The classmates who can see here must be true love.

From the above process, it can be seen that the Dart is not much different from other languages. It also shows that it is very easy for developers of other languages to switch to Dart

Coupled with the Dart in the field of client development, it is definitely no longer a dream to complete the client and server in one language.

That's All, Enjoy.


汤哥搞开发
34 声望4 粉丝