# ApiJsonSpringBoot **Repository Path**: sky0535/ApiJsonSpringBoot ## Basic Information - **Project Name**: ApiJsonSpringBoot - **Description**: 基于 Spring Boot 3 + APIJSON 8 的零代码 CRUD 演示项目 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2026-09-12 - **Last Updated**: 2026-09-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ApiJsonSpringBoot 基于 Spring Boot 3 + APIJSON 8 的零代码 CRUD 演示项目:前端只需发送 JSON 请求,后端自动生成并执行 SQL,无需为每张表写 Controller/Service/DAO。 # 环境 - SpringBoot 3.5.16 - Java 17 - APIJSON 8.1.7 / apijson-framework 8.1.7 - apijson-fastjson2 1.3.0(APIJSON 8.x 起 JSON 库抽离为插件,本项目使用 fastjson2 绑定) - fastjson 2.0.64(fastjson1 兼容包,为模型类提供 com.alibaba.fastjson.annotation.JSONField 注解) - mysql-connector-j 9.7.0 + HikariCP 6.3.3(HikariCP 版本由 Spring Boot BOM 管理) > APIJSON 相关依赖已升级至 8.1.7,后续可自行升级到更新版本,升级后注意回归测试。 # 和其他例子的主要区别 - 复杂度介于 APIJSONBoot 和 APIJSONDemo 之间 - 完成了一些常用配置 - 简单鉴权 - samesite 策略 - HikariCP 多数据源配置 - 解决数字返回前端精度丢失问题 # 项目结构 ``` src/main/java/com/example/demo/ ├── ApiJsonApplication.java # 启动类:注册 DEFAULT_APIJSON_CREATOR、CORS 跨域、APIJSONApplication.init() ├── common/ │ ├── DemoParser.java # 请求解析器:重写 createObjectParser,直接创建 fastjson2 的 APIJSONObjectParser │ │ # (规避父类匿名 ObjectParser 被强转导致的 ClassCastException) │ └── DemoSQLExecutor.java # SQL 执行器:接管连接获取,按 @datasource 从 HikariCP 连接池取连接; │ # 重写 getValue,把 bigint / bigint unsigned 转 String,解决前端 JS 精度丢失 ├── config/ │ ├── DemoSQLConfig.java # SQL 配置:默认数据库 MySQL、默认 schema=apijson、gainDBVersion()=9.7.1 │ └── DemoDataSourceConfig.java # 3 个 HikariCP 数据源 Bean:apijson(主)/db1/db2,对应 application.yml ├── controller/ │ └── DemoController.java # 路由入口:POST /{method} 万能接口、POST /{method}/{tag}、/login、/logout └── model/ └── Privacy.java # 用户隐私模型:phone、password、contactIdList(JSON 字段名由 @JSONField 指定) src/test/java/com/example/demo/ └── ApiJsonApplicationTests.java # Spring Boot 上下文加载冒烟测试 ``` 资源配置 `src/main/resources/application.yml`: - `spring.datasource.apijson / db1 / db2`:3 个 HikariCP 数据源(默认都指向 `jdbc:mysql://192.168.2.49:3306`,账号 dbuser,密码见配置文件) - `server.servlet.session.cookie`:`same-site: none` + `secure: true`,允许跨站携带 Cookie(配合 APIAuto 等前端工具调试) # 快速开始 1. 准备 MySQL:确保 `192.168.2.49:3306` 可连接,账号 `dbuser`,存在 `apijson` schema(APIJSON 启动时会自动初始化 Access/Request/Function 等系统表) 2. 启动项目: ```bash mvn spring-boot:run ``` 3. 测试(Postman / APIAuto,均为 POST + JSON): ``` POST http://localhost:8080/get { "Privacy": { "id": 82001 } } ``` > 注意:默认开启了登录校验(DemoController.newParser 中 `setNeedVerify(true)`), > 未登录访问会返回 `401 "xx 不允许 UNKNOWN 用户的 GET 请求"`,需先调 `/login` 或在 Access 表中为表配置 UNKNOWN 角色权限。 > 若返回 401 且确认配置无误,先检查 MySQL 是否已启动(数据库连不上时 Access 配置加载失败,所有表都会被拒)。 # APIJSON 语法速查 所有接口统一用 POST 提交 JSON,路由 `/{method}` 对应操作方式: | method | 用途 | 对应 SQL | |---|---|---| | get | 查单个对象 | SELECT ... WHERE id=... | | gets / gets_heads | 查列表 / 统计 | SELECT ... LIMIT | | post | 新增 | INSERT | | put | 修改 | UPDATE | | delete | 删除 | DELETE | ## 查询(get) ```json { "Privacy": { "id": 82001 } } ``` ## 字段过滤与别名 @column ```json { "Privacy": { "id": 82001, "@column": "id,phone:name" } } ``` `@column` 筛选返回字段;`phone:name` 把返回的 phone 改名为 name。 ## 逻辑筛选 {} 与范围 ```json { "[]": { "Privacy": { "id{}": [12, 15, 32], "@column": "id,phone" } } } ``` - `"id{}": [12,15,32]` — id IN (12,15,32) - `"id&{}" : ">=300,<=400"` — id >= 300 AND id <= 400 - `"id|{}" : "<=300,>=400"` — 逻辑或(默认) - `"id!{}" : [12]` — id NOT IN (12),用于反选/黑名单 ## 模糊查询 $ / ~ ```json { "[]": { "Privacy": { "phone$": "%tom%", "@column": "id,phone" } } } ``` - `"keyword%"` 开头匹配;`"%keyword"` 结尾匹配;`"%keyword%"` 包含 - `"phone~": "keyword"` 等价于 `"%keyword%"` 的便捷写法 ## 正则匹配 ? ```json { "[]": { "Privacy": { "phone?": "^[a-z0-9]+$", "@column": "id,phone" } } } ``` ## JSON 数组字段包含 <> ```json { "[]": { "Privacy": { "contactIdList<>": 82001, "@column": "id,contactIdList" } } } ``` 筛选 JSON 数组字段中包含指定值的记录。 ## 分页 ```json { "[]": { "Privacy": { "@column": "id,phone" }, "page": 0, "count": 5 } } ``` `page`(从 0 开始)和 `count`(每页条数)放在 `[]` 内。对应 SQL:`SELECT id,phone FROM apijson.Privacy LIMIT 5 OFFSET 0`。 特殊查询 `"query": 1` 只返回总数;`"query": 2` 返回全部数据,配合 `"total@": "/[]/total"` 取总数。 ## 排序 @order ```json { "[]": { "Privacy": { "@column": "id,phone", "@order": "id-,phone+" } } } ``` 字段顺序即排序优先级;`+` 升序(默认,可省略),`-` 降序。 ## 关联查询 id@ ```json { "[]": { "Moment": { "@column": "id,date,userId", "id": 12 }, "User": { "id@": "/Moment/userId", "@column": "id,name" } } } ``` `"id@": "/Moment/userId"` 表示 User.id 关联同层 Moment 对象的 userId 字段,一次请求返回主表 + 关联表数据。 ## 分组查询 @group 与聚合函数 支持函数:`count` `sum` `max` `min` `avg` ```json { "[]": { "Sale": { "@column": "store_id;sum(amt):totAmt", "@group": "store_id" } } } ``` 不分组时可直接 `"@column": "max(id):maxid"` 查聚合值。 ## 新增(post) ```json { "Privacy": { "phone": "tom", "password": "123456" }, "tag": "Privacy" } ``` `tag` 对应 Request 表中配置的校验规则名;返回的 id 即新增记录的 id。 > 本项目 Privacy 模型的密码字段在 JSON 中名为 `password`(见 Privacy.java 的 @JSONField)。 ## 修改(put) ```json { "Privacy": { "id": 1544520921923, "phone": "tommy" }, "tag": "Privacy" } ``` JSON 数组字段支持增删元素:`"contactIdList+": [123]` 追加,`"contactIdList-" : [123]` 移除。 ## 删除(delete) ```json { "Privacy": { "id": 1544520921923 }, "tag": "Privacy" } ``` ## 登录 / 退出 ### 登录 ``` POST http://localhost:8080/login ``` 请求体: ```json { "type": 1, "phone": "13000038710", "password": "666666", "version": 1, "format": false, "remember": true, "defaults": { "@database": "MYSQL", "@schema": "apijson" } } ``` 字段说明: | 字段 | 必填 | 说明 | |---|---|---| | `phone` | 是 | 手机号,不能为空(对应 Privacy 表 phone 字段) | | `password` | 是 | 密码,需通过 `StringUtil.isPassword` 校验(本项目为明文比对,生产环境建议改用 BCrypt 等加密) | | `remember` | 否 | 是否保持登录。true 时 Session 最大非活动时间为 72 小时,false 为 12 小时 | | `version` / `format` | 否 | 全局默认版本号 / 全局格式化配置,登录后写入 Session 供后续请求使用 | | `defaults` | 否 | 给后续每个请求 JSON 最外层默认加的字段(如 `@database`、`@schema`) | | `type` | 否 | 业务自定义字段,框架不处理 | 登录流程([DemoController.login](src/main/java/com/example/demo/controller/DemoController.java)): 1. 解析并校验 phone / password; 2. 用 HEADS 查 Privacy 判断用户名是否已注册; 3. 用 GETS 查出 Privacy 完整记录得到 userId; 4. 用 HEADS 按 `id + password` 验证密码; 5. 登录信息写入 Session(`userId`、`Privacy` 对象、`remember`),并设置 Session 过期时间。 成功响应(实测,HEADS 验证密码后返回的是其内层结果,故不含 Privacy 用户信息,只有 `count`): ```json { "ok": true, "code": 200, "msg": "success", "count": 1, "remember": true, "defaults": { "@database": "MYSQL", "@schema": "apijson" } } ``` 常见失败响应: | 场景 | code | msg | |---|---|---| | 手机号/密码格式不合法 | 500 | 手机号不合法!/ 密码不合法! | | 用户名不存在 | 500 | 用户名未注册 | | 密码错误 | 500 | 账号或密码错误 | > 注意:login 内部直接用 `new DemoParser(HEADS/GETS, false)` 创建解析器,不能用 `new APIJSONParser(...)`——后者会在 `createObjectParser` 中触发 `ClassCastException`(详见本项目特性说明的 fastjson2 适配)。 > 同时 `Privacy` 类上的 `@MethodAccess(GETS = {}, HEADS = {})` 注解必不可少,否则 `new JSONRequest(new Privacy()...)` 会因空 key put 未注解对象而抛 `IllegalArgumentException`;该限制只禁止外部请求直接批量查 Privacy,不影响 login 内部查询(needVerify=false)。 ### 退出 ``` POST http://localhost:8080/logout ``` 销毁服务端 Session 并返回登出结果;未登录时调用返回 500 "已经退出登录"。 登录成功后会话写入 Session(Cookie 已配置 SameSite=None,跨站调试可携带)。 # 本项目特性说明 - **fastjson2 适配**:APIJSON 8.x 将 JSON 库抽离为插件,自定义组件统一继承 `apijson.fastjson2` 包下的 `APIJSONParser / APIJSONObjectParser / APIJSONSQLConfig / APIJSONSQLExecutor`。`DemoParser` 重写了 `createObjectParser`,是因为 fastjson2 插件默认实现会把 framework 层的匿名 ObjectParser 强转为 fastjson2 类型而抛 `ClassCastException`,此处直接创建 fastjson2 的 `APIJSONObjectParser` 规避。 - **多数据源**:请求 JSON 中加 `"@datasource": "db1"` 或 `"db2"` 即可切换到对应数据源(见 DemoSQLExecutor.getConnection);缺省走主数据源 apijsonDataSource。 - **bigint 精度**:数据库中 bigint unsigned / 长度超过 15 位的 bigint 会转为 String 返回,避免 JS Number 精度丢失(见 DemoSQLExecutor.getValue)。 - **鉴权**:未登录用户为 UNKNOWN 角色,表级权限由数据库 Access 表配置;`DemoController.newParser` 中 `setNeedVerify(true)` 开启校验,新手调试可改为 `false`(线上不建议)。 # 参考 - APIJSON 语法文档:http://apijson.cn/doc/zh/grammar.html - APIJSON 设计规范:https://github.com/Tencent/APIJSON/blob/master/Document.md - 在线调试工具 APIAuto:http://apijson.cn/api