摘要: 前言 之前的博客一直都还没写到框架的实现及权限系统,今天开始写我的权限系统,我以前做过的项目基本上都有权限管理这个模块,但各个系统都会有一些不太一样,有些简单点,有些稍微复杂一点,一句话,我们做的系统都离不开这个权限系统。所以网上很多人尝试做一个通用的权限系统,不评论他们做的怎么样,只是说在网上能找到的直接能用的应该不多,适用的并且能集成到项目中的就更少了,所以还是考虑自己做一个,不一定很通用,但足够自己用的权限系统。 二、需求分析 关于权限系统的文章网上多如牛毛,很多都是基于角色的访问控制(RBAC)设计。但是发现完全实现RBAC的理论其实不一定好用,我想做一款适合自己的。所以首先我们必要明确我们要去实现哪些东西。 1、权限资源 a.菜单权限 经理和业务员登陆系统拥有的功能菜单是不一样的 b.按钮权限 经理能够审批,而业务员不可以 c.数据权限 A业务员看不到B业务员的单据 d.字段权限 某些人查询客户信息时看不到客户的手机号或其它字段 阅读全文
posted @ 2013-07-22 01:13 萧秦 阅读(62268) 评论(137) 推荐(433) 编辑

一、背景问题

自nodejs诞生以来出现了一大批的web框架如express koa2 egg等等,前端可以不再依赖后端可以自己控制服务端的逻辑。原来的后端开发同学的阵地前端如今同样也写的风生水起,撸起袖子就是干几周一个项目前端、后端都自己搞定了,那叫一个效率。

虽然各框架都提供了一些自己的接口去简化CRUD操作,但是还是没有解决复杂条件查询、服务端分页等等问题,导致开发过程中很多开发者还是直接拼接SQL来访问数据库。于是我们就想如何让访问数据库变得简单易用。

二、类库设计

操作数据库都可以看作跟数据库做一次交互,交互传递的是数据+命令,我们希望能够用尽量简洁的代码来描述每一次访问数据库。所以可以对mysqljs对数据库操作进行封装,重新设计访问数据库的API。为nodejs访问mysql数据库提供强大流畅的api的工具类库,目标是希望访问数据库逻辑都能使用一行代码完成,让访问数据库变得更加简单优雅。开源地址:https://github.com/liuhuisheng/ali-mysql-client

1. 初始化配置

初始化如下

const db = new DbClient({
  host     : '127.0.0.1',
  user     : 'root',
  password : 'secret',
  database : 'my_db'
});

2. 构造查询

  • 2.1 查询单个值
// 查询单个值,比如下面例子返回的是数字51,满足条件的数据条数
const result = await db
  .select("count(1)")
  .from("page")
  .where("name", "测试", "like")
  .queryValue();
  • 2.2 查询单条数据
// 查询单条数据,返回的是 result = {id:12, name: '测试页面', ....}
const result = await db
  .select("*")
  .from("page")
  .where("id", 12) // id = 12
  .queryRow();
  • 2.3 查询多条数据
// 查询多条数据 返回的是 ressult = [{...}, {...}];
const result = await db
  .select("*")
  .from("page")
  .where("name", "测试页面", 'like') // name like '%测试页面%'
  .queryList();
  • 2.4 服务端分页查询
// 查询多条数据(服务端分页) 返回的是 ressult = {total: 100, rows:[{...}, {...}]};
const result = await db
  .select("*")
  .from("page")
  .where("id", 100, "lt") // id < 100
  .queryListWithPaging(3, 20); //每页 20 条,取第 3 页
  • 2.5 多表关联查询
// 多表关联查询
const result = await db
  .select("a.page_id, a.saga_key")
  .from("page_edit_content as a")
  .join("left join page as b on b.id = a.page_id")
  .where("b.id", 172)
  .queryList();
  • 2.6 查询除了支持各种多表join外,当然还支持groupby orderby having等复杂查询操作
const result = await db
  .select("a1 as a, b1 as b, count(c) as count")
  .from("table")
  .where("date", db.literals.now, "lt") // date < now()
  .where("creator", "huisheng.lhs")  // creator = 'huisheng.lhs"
  .groupby("a1, b1")
  .having("count(category) > 10")
  .orderby("id desc")
  .queryListWithPaging(2); //默认每页20条,取第2页

3. 构造插入

const task = {
  action: "testA",
  description: "desc1",
  state: "123",
  result: "result1"
};

// 插入一条数据
const result = await db
  .insert("task", task)
  .execute();

// 也支持直接写字段,支持增加字段
const result = await db
  .insert("task")
  .column("action", "test")
  .column("create_time", db.literals.now)
  .execute();

// 插入多条数据
const tasks = [ task1, taks2, task3 ];
const result = await db
  .insert("task", tasks)
  .execute();

// 支持增加或覆盖字段
const result = await db
  .insert("task", tasks)
  .column('create_time', db.literals.now)  // 循环赋值给每一行数据
  .column('create_user', 'huisheng.lhs')
  .execute();

4. 构造更新

const task = {
  action: "testA",
  description: "desc1",
  state: "123",
  result: "updateResult"
};

//更新数据
const result = await db
  .update("task", task)
  .where("id", 1)
  .execute();

//更新数据,支持增加字段
const result = await db
  .update("task")
  .column("action", "test-id22")
  .column("create_time", db.literals.now)
  .where('id', 2)
  .execute();

5. 构造删除

//删除id为1的数据
const result = await db
  .delete("task")
  .where("id", 1)
  .execute();

6. 事务控制

const trans = await db.useTransaction();

try {
  // 数据库操作
  // await trans.insert(...)
  // await trans.update(...)
  await trans.commit();
} catch (e) {
  await trans.rollback();
}

7. 复杂条件查询设计

7.1 查询条件所有参数说明

// 查询条件所有参数
const result = await db
  .where(field, value, operator, ignore, join) // 支持的所有参数
  .where({field, value, operator, ignore, join}) //支持对象参数
  .queryList();
  
// 复杂查询条件
const result = await db
  .select("*")
  .from("page")
  .where("id", 100, "gt") // id > 100
  .where("tags", "test", "like") //name like '%test%'
  .where("tech", tech, "eq", "ifHave") // tech='tech_value' 当 tech 为空时,不做为查询条件
  .where("tags", tags, "findinset", "ifHave", "or")
  .queryList();
  • field 字段名
  • value 传入值
  • operator 操作符,默认equal4
  • ignore 是否加为条件,返回false时则忽略该条件
  • join 连接符号(and or),默认为and

7.2 操作逻辑定义operator

该参数很好理解,默认值为equal,支持传字符串或传入函数,传入字符串则会匹配到已定义的逻辑,

const result = await db
  .select("*")
  .from("page");
  .where("id", 100, "lt")  // id < 100
  .where("group_code", "dacu") // group_code = "dacu"
  .queryList();

大家能理解operator是为拼接查询条件使用的逻辑封装,复杂条件的拓展能力都可以靠自定义的operator来完成。其函数的形式如下:

const customOperator =  ({ field, value }) => {
  if (condition) {
    return {
      sql: '?? = ?',
      arg: [ field, value ],
    };
  } else {
    return {
      sql: '?? > ?',
      arg: [ field, value ],
    };
   }
};

// 可直接使用也可注册到全局
const config = db.config();
config.registerOperator("customOperator", customOperator);

7.3 是否加为条件ignore

这个需要解释下,当满足xx条件时则忽略该查询条件,ignore设计的初衷是为了简化代码,比如以下代码是很常见的,界面上有输入值则查询,没有输入值时不做为查询条件:

const query = db
  .select("*")
  .from("page");
  .where("id", 100, "lt");

if (name){
    query.where("name", name, 'like');
}

if (isNumber(source_id)){
    query.where('source_id', source_id)
}

const result = await query.queryList();

上面的代码使用ignore时则可简化为:

const result = await db
  .select("*")
  .from("page")
  .where("id", 100, "lt")
  .where("name", name, "like", "ifHave") //使用内置 ifHave,如果name为非空值时才加为条件
  .where("source_id", tech, "eq", "ifNumber") //使用内置 ifNumber
  .queryList();

支持传字符串或传入函数,传入字符串则会匹配到已定义的逻辑,其函数的形式如下:

const customIgnore = ({field, value}) => {
    if (...){
        return false;
    }
    
    return true;
};

//也可以注册到全局使用
const config = db.config();
config.registerIgnore("customIgnore", customIgnore);

7.4 查询条件优先级支持

// where a = 1 and (b = 1 or c < 1) and d = 1
const result = await db.select('*')
  .from('table')
  .where('a', 1)
  .where([
    {field: 'b', value: '1', operator:'eq'},
    {field: 'c', value: '1', operator:'lt', join: 'or'},
  ])
  .where('d', 1)
  .queryList();

7.5 真实场景中的复杂查询示例

// 复杂查询,真实场景示例,项目中拓展了keyword、setinset等operator及ignore
const result = await app.db
  .select('a.*, b.id as fav_id, c.name as biz_name, d.group_name')
  .from('rocms_page as a')
  .join(`left join favorite as b on b.object_id = a.id and b.object_type = "rocms_page" and b.create_user = "${this.ctx.user.userid}"`)
  .join('left join rocms_biz as c on c.biz = a.biz')
  .join('left join rocms_biz_group as d on d.biz = a.biz and d.group_code = a.biz_group')
  // 关键字模糊查询
  .where('a.name,a.biz,a.biz_group,a.support_clients,a.owner,a.status', query.keywords, 'keywords', 'ifHasValueNotNumber') // 关键字在这些字段中模糊查询
  .where('a.id', query.keywords, 'eq', 'ifNumber') // 关键字中输入了数字时当作id查询
  // 精确查询
  .where('a.id', query.id, 'eq', 'ifHave')
  .where('a.name', query.name, 'like', 'ifHave')
  .where('a.biz', query.biz, 'eq', 'ifHave')
  .where('a.biz_group', query.biz_group, 'eq', 'ifHave')
  .where('a.support_clients', query.support_clients, 'setinset', 'ifHave')
  .where('a.status', query.status, 'insetfind', 'ifHave')
  .where('a.owner', query.owner, 'eq', 'ifHave')
  .where('a.offline_time', query.owner, 'eq', 'ifHave')
  // TAB类型 我的页面own、我的收藏fav、所有页面all
  .where('a.owner', this.ctx.user.userid, 'eq', () => query.queryType === 'own')
  .where('b.id', 0, 'isnotnull', () => query.queryType === 'fav')
  // 分页查询
  .orderby('a.update_time desc, a.id desc')
  .queryListWithPaging(query.pageIndex, query.pageSize);

4. 自定义配置

const config = db.config();

// 自定义operator
config.registerOperator('ne', ({ field, value }) => {
  return { sql: '?? <> ?', arg: [ field, value ] };
});

// 自定义ignore
config.registerIgnore('ifNumber', ({ value }) => {
  return !isNaN(Number(value));
});

// 监听事件 执行前
config.onBeforeExecute(function({ sql }) {
  console.log(sql);
});

// 监听事件 执行后
config.onAfterExecute(function({ sql, result }) {
  console.log(result);
});

// 监听事件 执行出错
config.onExecuteError(function({ sql, error }) {
  console.log(error);
});

5. 内置的operator及ignore

  • 内置的默认operator

    • eq (equal)
    • ne (not equal)
    • in (in)
    • gt (greater than)
    • ge (greater than or equal)
    • lt (less than)
    • le (less than or equal)
    • isnull (is null)
    • isnotnull (is not null)
    • like (like)
    • startwith (start with)
    • endwith (end with)
    • between (between)
    • findinset (find_in_set(value, field))
    • insetfind (find_in_set(field, value))
    • sql (custom sql)
    • keywords (keywords query)
  • 内置的默认ignore

    • ifHave (如果有值则加为条件)
    • ifNumber (如果是数值则加为条件)

三、使用示例

在eggjs中可以和egg-mysql一起使用,初始化时支持直接传入egg-mysql或ali-rds对象,避免重复创建连接池。

// this.app.mysql 为egg-mysql对象
const db = new DbClient(this.app.mysql)

四、项目开源

该项目最初是在自己项目内部使用只发布在内网中,最近整理去除了内网依赖开源到github上,ali-mysql-client:https://github.com/liuhuisheng/ali-mysql-client

该类库旨在为nodejs访问mysql数据库提供强大流畅的api,目标是希望访问数据库逻辑都能使用一行代码完成,让访问数据库变得更加简单优雅。有任何意见或建议欢迎大家可以随时反馈给我。

posted @ 2019-07-22 08:53 萧秦 阅读(2725) 评论(1) 推荐(3) 编辑
摘要: 概述 数据监听实现上就是当数据变化时会通知我们的监听器去更新所有的订阅处理,数据监听是对观察者模式的实现,也是MVVM中的核心功能。这个功能我们在很多场景中都可以用到,可以大大的简化我们的代码。 现有MVVM框架中的Observable是怎么实现的 先看看各MVVM框架对Observable是怎么实现的,常见的MVVM框架有以下几种,我们分析下它们的实现原理 阅读全文
posted @ 2016-09-18 17:49 萧秦 阅读(6231) 评论(2) 推荐(7) 编辑
摘要: 一、前言 半年前左右折腾了一个前后端分离的架子,这几天才想起来翻出来分享给大家。关于前后端分离这个话题大家也谈了很久了,希望我这个实践能对大家有点点帮助,演示和源码都贴在后面。 二、技术架构 这两年angularjs和reactjs算是比较火的项目了,而我选择angularjs并不是因为它火,而是因它的模块化、双向数据绑定、注入、指令等都是非常适合架构较复杂的前端应用,而且文档是相当的全,碰到问题基本上可以在网上都找到答案。所以前端基本思路就以angularjs为主、代码模块化,通过requirejs实现动态加载,ui选择dhtmlx为主配合少量bootstrap3使用。前端项目dhtmlx_web: 开发工具 Sublime Text 前端框架angularjs 模块加载requirejs 前端UI dhtmlx + bt3 包管理 bower 构建工具 gruntjs 服务架设 http_server.js 浏览器支持IE8+ 实际是为了支持IE8我做 阅读全文
posted @ 2015-10-09 08:44 萧秦 阅读(28056) 评论(74) 推荐(194) 编辑
摘要: 一、前言 前面已经给大家介绍了我的工作流引擎的总体设计及的API设计,这篇是实战篇说说怎么实际应用了,这就得涉及到UI界面了。首先我们常用的工作流个人办公应用系统至少要包括发起流程、待办事项、已办事项等。我们设计了一个尽量简单的系统,能够满足个人办公的基本需求,只实现以下功能: 1、发起流程 2、待办事项 3、已办事项 4、我的流程 5、我的消息 6、我的委办 这些功能基本就是流程能正常的运行流转基础,其实只应用了我们引擎的一小部分功能,其它功能如果有需要可以在这基本上再添加。 阅读全文
posted @ 2015-09-29 10:13 萧秦 阅读(8420) 评论(14) 推荐(33) 编辑
摘要: 一、前言 上一篇我给大家介绍了我的工作流的模型和基本的设计,这篇我想详细说明下我这款工作流的功能及使用示例。这款工作流主要是面向开发者设计的,为了先让大家有个全局的认识,局部功能的设计实现就不细说了,后续有时间我会继续写文章向大家介绍。 二、功能详解及使用示例代码 1、配置流程引擎,一般在程序启动过程中调用(Global.asax.cs中) 阅读全文
posted @ 2015-09-07 08:28 萧秦 阅读(14634) 评论(35) 推荐(52) 编辑
摘要: 一、前言 提到工作流很多人就会想到OA,的确OA就是典型的工作流的应用,但是工作流并不仅仅局限于OA,工作流应该算是基础框架软件,主要用于流程的重组和优化,它有广阔的应用领域。在java下有很多优秀的开源工作流可以选择比如activit5、jpbm4等,在.net下却几乎找不到令人满意的工作流引擎可用。当然不是说.net下没有开源的只是有些国产开源的但看了代码后就一点兴趣都没有了,且不说代码质量如何,还引入了一大堆的东西,想在项目中应用也是非常困难。鉴于此我还是决定自己开发一款.NET微型工作流引擎。 二、基本说明 为什么叫微型工作流引擎?就是超轻量级,以方便在项目中轻便的使用,比如只有一个类库dll,大小也就几百k到1M左右 阅读全文
posted @ 2015-07-13 11:03 萧秦 阅读(29909) 评论(60) 推荐(79) 编辑
摘要: 一、为什么需要路由优先级 大家都知道我们在Asp.Net MVC项目或WebApi项目中注册路由是没有优先级的,当项目比较大、或有多个区域、或多个Web项目、或采用插件式框架开发时,我们的路由注册很可能不是写在一个文件中的,而是分散在很多不同项目的文件中,这样一来,路由的优先级的问题就突显出来了。 比如: App_Start/RouteConfig.cs中 routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); Areas/Admin/AdminAreaRegistration.cs中 context.MapRoute( name: "Login", url: "login", defaults: new { area = "Admin", c 阅读全文
posted @ 2015-07-08 07:42 萧秦 阅读(10748) 评论(20) 推荐(33) 编辑
摘要: 常用的的编码有: 1、数据库自增长ID或最大值加1 2、GUID 3、时间戳 4、常量+自增长 5、常量+时间戳+自增长 6、根据单据属性编码 比如商品编码:第X是代码商品颜,第Y位是代码商品产地 7、自定义函数处理返回 8、其它 阅读全文
posted @ 2014-05-05 07:41 萧秦 阅读(20723) 评论(11) 推荐(37) 编辑
摘要: 作为一个码农这么多年,一直在想怎么提高我们的编码效率,关于如何提高编码效率,我自己的几点体会 1、清晰的项目结构,要编写代码的地方集中 2、实现相同功能的代码量少并且清晰易懂 3、重复或有规律的代码应该自动生成 在这里我就讨论下代码生成的问题。 刚毕业时我也非常迷信代码生成器,喜欢在网上找一些代码生 阅读全文
posted @ 2014-02-18 07:56 萧秦 阅读(29315) 评论(40) 推荐(146) 编辑
摘要: CSS 预处理器技术已经非常的成熟,常用的预处理器框架有: 1、Less 官网:http://lesscss.org/ 2、Sass 官网:http://sass-lang.com/ 3、Stylus 官网:http://learnboost.github.io/stylus/ 我研究比较多的只有less,后两者也只是了解了下,所以这里我还是选用less来实现 阅读全文
posted @ 2013-12-03 08:14 萧秦 阅读(13827) 评论(16) 推荐(48) 编辑
点击右上角即可分享
微信分享提示