Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to make another APIJSON? / 如何实现其它语言的APIJSON? #38

Open
TommyLemon opened this issue Jun 20, 2018 · 26 comments
Open
Assignees
Labels
Enhancement 增强 增强功能、提高性能等

Comments

@TommyLemon
Copy link
Collaborator

TommyLemon commented Jun 20, 2018

目前已有 C#, Go, Java, Node, PHP, Python 对应的 APIJSON ORM 库实现
https://github.com/Tencent/APIJSON#%E7%94%9F%E6%80%81%E9%A1%B9%E7%9B%AE

一开始不要贪大求全,实现基础的功能就好,功能后面再完善、性能后面再优化。

可以参考Java版的实现,建议从这个版本做起
https://github.com/TommyLemon/APIJSON/tree/636fe2263c01acce4ad32751cbb8a579830d9f29

这个是最简单的实现,只有7个类,将JSON转换为SQL,但也能够实现最核心的 引用赋值、单表查询、数组分页查询 了。
先完成这个,后面再优化。如果直接实现复杂功能,很可能导致一大堆问题然后坚持不下去。

@TommyLemon TommyLemon added the Enhancement 增强 增强功能、提高性能等 label Jun 20, 2018
@TommyLemon
Copy link
Collaborator Author

TommyLemon commented Jun 20, 2018

image

建议步骤:

1.实现单表查询

{
    "User": {
        "id": 82002
    }
}

转换为

SELECT * FROM User WHERE id = 82002 LIMIT 1 OFFSET 0

ResultSet取第0条(总数最多1条),取出所有字段对应的值,封装为一个表对象返回:

{
    "id": 82002,
    "sex": 1,
    "name": "Happy~",
    "tag": "iOS",
    "head": "http://static.oschina.net/uploads/user/1174/2348263_50.png?t=1439773471000",
    "contactIdList": [
        82005,
        82001,
        38710
    ],
    "pictureList": [],
    "date": "2017-02-01 19:21:50.0"
}

替换原来的

{
    "id": 82002
}

2.实现数组查询

{
    "[]": {
        "count": 10,
        "page": 1,
        "User": {
            "sex": 1
        }
    }
}

转换为

SELECT * FROM User WHERE sex = 1 LIMIT 10 OFFSET 10

ResultSet取所有,封装为一个包含表对象的数组返回:

[
    {
        "User": {
            "id": 82005,
            "sex": 1,
            "name": "Jan",
            "tag": "AG",
            "head": "http://my.oschina.net/img/portrait.gif?t=1451961935000",
            "contactIdList": [
                82001,
                38710
            ],
            "pictureList": [],
            "date": "2017-02-01 19:21:50.0"
        }
    },
    {
        "User": {
            "id": 82006,
            "sex": 1,
            "name": "Meria",
            "head": "http://static.oschina.net/uploads/user/998/1997902_50.jpg?t=1407806577000",
            "contactIdList": [],
            "pictureList": [],
            "date": "2017-02-01 19:21:50.0"
        }
    }
]

替换原来的

{
    "count": 10,
    "page": 1,
    "User": {
        "sex": 1
    }
}

3.实现两张表 一对一 关联查询

{
    "Comment": {},
    "User": {
        "id@": "Comment/userId"
    }
}

先解析Comment,转换为

SELECT * FROM Comment LIMIT 1 OFFSET 0

ResultSet取第0条(总数最多1条),取出所有字段对应的值,封装为一个Comment表对象返回:

{
    "id": 4,
    "toId": 0,
    "userId": 38710,
    "momentId": 470,
    "date": "2017-02-01 19:20:50.0",
    "content": "This is a Content...-4"
}

然后解析User,解析到

"id@": "Comment/userId"

时,从Comment中取出userId,把以上键值对改为

"id": 38710

然后把User转换为

SELECT * FROM User WHERE id = 38710 LIMIT 1 OFFSET 0

ResultSet取第0条(总数最多1条),取出所有字段对应的值,封装为一个User表对象返回:

{
    "id": 38710,
    "sex": 0,
    "name": "TommyLemon",
    "tag": "Android&Java",
    "head": "http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000",
    "contactIdList": [
        82003,
        82005,
        70793
    ],
    "pictureList": [
        "http://static.oschina.net/uploads/user/1218/2437072_100.jpg?t=1461076033000",
        "http://common.cnblogs.com/images/icon_weibo_24.png"
    ],
    "date": "2017-02-01 19:21:50.0"
}

4.实现两张表 一对多 关联查询

{
    "Moment": {},
    "[]": {
        "Comment": {
            "momentId@": "Moment/id"
        }
    }
}

5.实现两张表在数组内 一对一 关联查询

{
    "[]": {
        "Comment": {},
        "User": {
            "id@": "[]/Comment/userId"
        }
    }
}

其中

"id@": "[]/Comment/userId"

要根据Comment在数组中的位置index来动态变为

"id@": "[]/0/Comment/userId"
"id@": "[]/1/Comment/userId"

...

6.实现两张表在数组内 一对多 关联查询

实现了 1-6 后可以测试是否能通过这个用例(一对一、一对多、数组外、数组内、同层级、跨层级都有)
963D6EF9B87189AA9AD5FEEE84CC3149

http://apijson.cn/api

POST http://apijson.cn:8080/get

{
    "Moment": {
        "id": 32,
        "@column": "id,userId,praiseUserIdList"
    },
    "User": {
        "id@": "Moment/userId",
        "@column": "id"
    },
    "[]": {
        "Moment": {
            "userId{}@": "Moment/praiseUserIdList",
            "@column": "id,userId"
        },
        "User": {
            "id@": "/Moment/userId",
            "@column": "id,name,head"
        },
        "Comment[]": {
            "Comment": {
                "momentId@": "[]/Moment/id"
            }
        }
    }
}

7.实现SQL的 column, order by, group by等功能。

8.实现增、删、改

APIAuto 上有 200 左右测试用例,可以跑一遍看看哪些有问题
http://apijson.cn/api

管理员账号及密码 13000082001/123456
image

管理员账号及密码 13000088888/123456
image

测试数据 SQL 文件(MySQL, PostgreSQL, Oracle, SQL Server, DB2, ClickHouse 等)
https://github.com/APIJSON/APIJSON-Demo

完整功能见设计规范(核心的任意多表关联查询功能完成后可逐步完善其它功能)
https://github.com/Tencent/APIJSON/blob/master/Document.md#3

@TommyLemon
Copy link
Collaborator Author

TommyLemon commented Jun 20, 2018

由于不限制嵌套层级和组合方式,所以必须用递归实现,从数据库查到结果(一开始可模拟测试数据,省去连接数据库的过程)后替换掉原来的内容再返回。

递归时由于不知道键值对key:value中的key和value,所以需要判断key和value的格式。

{
    "[]": { // value 类型为 JSONObject  &&  key 以 [] 结束
        "User": { // value 类型为 JSONObject  &&  key 符合正则表达式  ^[A-Za-z]+$
            "sex": 1 // value 类型不为 JSONObject
        }
    }
}

@TommyLemon
Copy link
Collaborator Author

TommyLemon commented Jun 20, 2018

最简单的解析过程可以是:

1.Parser递归遍历 请求JSON,提取出每个 表对象JSON
2.Parser用 表对象JSON 封装SQLConfig,交给SQLExecutor
3.SQLExecutor用SQLConfig封装SQL语句,并连接数据库执行
4.SQLExecutor返回执行结果给Parser
5.Parser替换对应请求JSON中的对象

@TommyLemon
Copy link
Collaborator Author

TommyLemon commented Jul 4, 2018

APIJSON-C# Server:
创作不易,给热心的作者右上角点 ⭐Star 支持下吧 ^_^
https://github.com/liaozb/APIJSON.NET

@zeromake
Copy link

zeromake commented Aug 9, 2018

@TommyLemon

上回说的python初步完成了 https://github.com/zeromake/restful_model 刚刚把单元测试写好。
json 的表现层完全自定义,都是为了对应 sql

import sqlalchemy as sa
from sanic import Sanic
from restful_model import DataBase
from restful_model.extend.sanic import ApiView

metadata = sa.MetaData()
# 这个model可以直接用工具连接数据库生成
User = sa.Table(
    'user',
    metadata,
    sa.Column(
        'id',
        sa.Integer,
        autoincrement=True,
        primary_key=True,
        nullable=False,
    ),
    sa.Column(
        'account',
        sa.String(16),
        nullable=False,
    ),
    sqlite_autoincrement=True,
)
# python 可以动态生成 class 后面想批量注册model到view也很简单
class UserView(ApiView):
    __model__ = User
    # 请求方法过滤
    # __method__ = ["get", "post"]
    # 过滤 where 或者查询字段,支持全局或单个方法过滤
    #__filter_keys__ = {"get": [["id"]]}
    # 中间件模式
    async def auth_filter(self, context: Context, next_filter):
        return await next_filter()
app = Sanic(__name__)
db = DataBase("sqlite:///db.db")
app.db = db

@app.listener('before_server_start')
async def setup_db(app, loop):
    if app.db.loop is None:
        app.db.loop = loop
        app.db.engine = await app.db.create_engine(echo=True)
        if not await app.db.exists_table(User.name):
            await app.db.create_table(User)

userView = UserView.as_view(app.db)
app.add_route(userView, "/user", HTTP_METHODS)
app.add_route(userView, "/user/<id:int>", HTTP_METHODS)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8000)
$ # create
$ curl -X POST http://127.0.0.1:8000/user \
-H 'content-type: application/json' \
-d '{ "account": "test1" }'
> {
    "status": 201,
    "message": "Insert ok!",
    "meta": {"count":1}
}
$ # select
$ curl -X GET http://127.0.0.1:8000/user
> {
    "status": 200,
    "message": "Query ok!",
    "data": [{
        "id": 1,
        "account": "test1"
    }]
}
$ # update
$ curl -X PUT http://127.0.0.1:8000/user \
-H 'content-type: application/json' \
-d '{"where": {"id": 1}, "values": {"account": "test2"}}'
> {
    "status": 201,
    "message": "Update ok!",
    "meta":{
        "count": 1
    }
}
$ curl -X GET http://127.0.0.1:8000/user
> {
    "status": 200,
    "message": "Query ok!",
    "data": [
        {"id": 1,"account": "test2"}
    ]
}
$ # delete
$ curl -X DELETE http://127.0.0.1:8000/user \
-H 'content-type: application/json' \
-d '{"id": 1}'
> {
    "status": 200,
    "message": "Delete ok!",
    "meta": {"count":1}
}
$ curl -X GET http://127.0.0.1:8000/user
> {
    "status": 200,
    "message": "Query ok!",
    "data": []
}

现在还是觉得权限管理不太给力,虽然用中间件模式,什么样的权限都做的了。

以及多表连接在想是提供一个 class 然后把多张表设置上去,url 和权限独立,还是像你的 APIJSON 全局注册所有表随意连接任意表。
我这个库就是一个 python 库,然后不对任意一个 web 框架强绑定。
现在写了 Sanic, Tornado 的支持。

后面有心情再考虑把 json 的表现层抽出来,做了 APIJSON 的 json 表现层。
现在还有个问题就是 Date 的格式化函数 sqlite, mysql, pg 每个都不一样,现在想要么都不用,全用 python 的格式..

@TommyLemon
Copy link
Collaborator Author

TommyLemon commented Aug 9, 2018

@zeromake 赞,已Star。
不过URL里不要加user等符号哦,应该放到请求JSON里面,方便传任意表名

{
    "User": {
        "id": 70793
    }
}
{
    "Moment": {
        "id": 12
    }
}

...
还能自由组合

{
    "Moment": {
        "id": 12
    },
    "User": {
        "id": 70793
    }
}

不知道User.id的情况下可以通过 引用赋值 得到

{
    "Moment": {
        "id": 12
    },
    "User": {
        "id@": "Moment/userId" // User.id = Moment.userId
    }
}
{
    "Moment": {
        "id": 12
    },
    "[]": {
        "User": {
            "id{}@": "Moment/praiseUserIdList" // json_contains(Moment.praiseUserIdList, User.id)
        }
    }
}

参考上面的 建议步骤设计规范

@TommyLemon
Copy link
Collaborator Author

TommyLemon commented Aug 9, 2018

@zeromake 你改好后我就在APIJSON主页加上这个项目的链接哈,和 C#版 一样
https://github.com/TommyLemon/APIJSON

@zeromake
Copy link

zeromake commented Aug 9, 2018

@TommyLemon 我这个只是为了做基础控件的,对应某个表操作,全局可连接任意表的应当后面做一个单独的控制 view,全部由使用的人来选择是注册一个全局 url 进行控制,还是挂几个表view。后面都会做对应的包括单表和 APIJSON 的相同的全局可连接任意表。

@TommyLemon
Copy link
Collaborator Author

@zeromake 这样啊,非常期待,记得通知我哦,有问题欢迎找我交流

@zeromake
Copy link

@TommyLemon
issues交流比较麻烦,base64解密防搜索引擎
加我 wx:YWZseTM5MA== ;或者qq:MzkwNzIwMDQ2

@TommyLemon
Copy link
Collaborator Author

@zeromake 可以

@TommyLemon
Copy link
Collaborator Author

APIJSON Node.js 版,基于 typeorm ,使用 TypeScript 实现。

创作不易,给热心的作者右上角点 ⭐Star 支持下吧 ^_^
https://github.com/TEsTsLA/apijson

@yuu2lee4
Copy link

已赞 @TommyLemon

@TommyLemon
Copy link
Collaborator Author

已经有热心的开发者实现了 PHP 版的 APIJSON 🎉
创作不易,给作者点 ⭐Star 支持下吧^_^
https://github.com/orchie/apijson

@never615
Copy link

never615 commented Dec 8, 2018

@TommyLemon 这个PHP版没有实现吧...

@never615
Copy link

建议出一个功能清单.md,放在每个语言实现的代码库里,实现的功能就划掉,这样比较清晰.

@yuhongshuai
Copy link

有没有打算做非关系数据库mongodb的

@TommyLemon
Copy link
Collaborator Author

@yuhongshuai 暂时没有计划,后续可能支持。

@TommyLemon
Copy link
Collaborator Author

已经有热心的开发者实现了 Python 版的 APIJSON 🎉
经测试,除了基本的查询(分页、排序等),还实现了自动化的权限控制。
最近作者又新增了自动化 API post。

创作不易,给作者点 ⭐Star 支持下吧^_^
https://github.com/zhangchunlin/uliweb-apijson

@TommyLemon TommyLemon pinned this issue Jan 25, 2019
@TommyLemon TommyLemon self-assigned this Mar 2, 2019
@TommyLemon
Copy link
Collaborator Author

@never615
另一个 PHP 版 APIJSON,看起来功能比原来那个多不少,可以试试,点 Star 支持下吧^_^
https://github.com/qq547057827/apijson-php

@TommyLemon
Copy link
Collaborator Author

APIJSON Go 语言版 APIJSON
https://github.com/crazytaxi824/APIJSON

@TommyLemon TommyLemon unpinned this issue Jan 3, 2021
@TommyLemon
Copy link
Collaborator Author

TommyLemon commented Feb 10, 2021

@never615 @SwankyTigerYY @y449329998 @ben1one
最近发现了一个新的 APIJSON PHP 版实现,代码和文档看起来比之前的都要完善一些,可以试试,能用的话就 Star 鼓励下作者~
https://github.com/xianglong111/json-api

image
image
image

@TommyLemon
Copy link
Collaborator Author

TommyLemon commented Mar 13, 2022

31D2FB32-85E1-42BA-863F-1A12857A0597
注:getQuote 是获取引号,避免表名、字段名等和关键词冲突,例如 MySQL 用反引号 `name`,PostgreSQL, Oracle, SQLServer, DB2 都可以用双引号 "date"

@glennliao
Copy link
Contributor

新增一个go版本的
https://github.com/glennliao/apijson-go

@TommyLemon TommyLemon changed the title 如何实现其它语言的APIJSON? 如何实现其它语言的APIJSON?/How to make another APIJSON? Nov 23, 2022
@TommyLemon TommyLemon changed the title 如何实现其它语言的APIJSON?/How to make another APIJSON? 如何实现其它语言的APIJSON?/ How to make another APIJSON? Nov 23, 2022
@TommyLemon TommyLemon changed the title 如何实现其它语言的APIJSON?/ How to make another APIJSON? How to make another APIJSON? / 如何实现其它语言的APIJSON? Nov 23, 2022
@jimisun123
Copy link

@TommyLemon 我想问一下,生成的SQL是有基于什么标准吗? 怎么才能做到生成的SQL兼容各种数据库?

@TommyLemon
Copy link
Collaborator Author

TommyLemon commented Aug 22, 2023

@TommyLemon 我想问一下,生成的SQL是有基于什么标准吗? 怎么才能做到生成的SQL兼容各种数据库?

@jimisun123
根据配置的数据库类型和版本适配,例如 Java 版是在 DemoSQLConfig 重新父类 AbstractSQLConfig 的方法 getDatabase, getDBVersion
优先 MySQL/PostgreSQL 等热门开源数据库的 SQL 语法和 JDBC/ODBC。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Enhancement 增强 增强功能、提高性能等
Projects
None yet
Development

No branches or pull requests

7 participants