# service-abc
**Repository Path**: linlurui/service-abc
## Basic Information
- **Project Name**: service-abc
- **Description**: 基于spring-cloud3的微服务领域驱动开发框架
- **Primary Language**: Java
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2022-05-20
- **Last Updated**: 2024-08-24
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# service-abc开发框架说明
* 基于spring-cloud3采用领域驱动开理念搭建的微服务快速开发框架。
* abc开发理念即api-business-clients,api提供接口服务传递基础数据到business处理业务逻辑,clients暴露接口相互调用型在闭环,abc三者之间引用关系形成良性循环。
# ABC设计理念

# ABC微服务架构

# 创建项目步骤
## 步骤一:新建项目


## 步骤二:Maven配置
* 点开修改pom.xml

### 1) 配置maven包下载仓库
```xml
true
always
true
always
ignore
abc-dev
https://gitee.com/linlurui/service-abc/raw/release
```
### 2) 配置maven插件仓库 (和repository配置成一样的)
```xml
true
always
true
always
ignore
abc-dev
https://gitee.com/linlurui/service-abc/raw/release
```
### 3) 添加maven插件配置
```xml
abc-dev
abc-generator
0.0.1-SNAPSHOT
${project.name}
/api
true
id,createdOn
tenant,user
deleted
lock_version
mp
HashMap
false
service-abc
clean
abc-generate
```
### 4) 项止主pom添加packaging
```xml
pom
```
## 步骤三:配置数据源
* 首次生成项目根目录下需新增application.yml配置如下数据源,生成目录结构后数据源配置会同步生成到abc-api目录下的application.yml,再次生成只需维护abc-api目录下的application配置项

```yaml
spring:
datasource:
# 动态数据源
dynamic:
primary: auth #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
# 多数据源属性
master:
url: jdbc:mysql://localhost:3306/master
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
auth:
url: jdbc:mysql://localhost:3306/auth
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
```
## 步骤四:刷新Maven并执行Clean

* 初始化数据库的脚本可以放在resources/sql/init.sql文件中, 如:
```sql
Create Database If Not Exists `auth` Character Set utf8mb4;
Create Table If Not Exists `auth`.`online` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '表id',
`account` varchar(16) NOT NULL COMMENT '登录账号',
`platform` varchar(32) NOT NULL COMMENT '登录平台',
`token` varchar(2048) DEFAULT NULL COMMENT '当前Token',
`login_time` datetime DEFAULT NULL COMMENT '登录时间',
`account_state` int DEFAULT 0 COMMENT '账号状态',
`ip` varchar(16) DEFAULT NULL COMMENT '登录IP',
`expir` bigint DEFAULT NULL COMMENT '过期时间',
`private_key` varchar(64) DEFAULT NULL COMMENT '私钥',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14503 DEFAULT CHARSET=utf8mb4 COMMENT='在线账号';
```
## 步骤五:启动项目
* 假设${project.name}为dev,展开abc-api下的abc.dev目录,运行ServiceAbcApplication

# 项目目录结构

* abc-api/controller (接口服务控制器)
* abc-api/service (数据库访问层)
* abc-api/service/impl (数据访问实现层)
* abc-api/mapper (数据访问映射层)
* abc-api/mapper/xml (数据访问映射实现,重新生成会被覆盖,增强需求需要用继承实现)
* abc-clients/to (数据传输实体,重新生成会被覆盖)
* abc-clients/vo (数据返回实体,默认继承to)
* abc-clients/client (feign客户端,重新生成会被覆盖)
* abc-business/bll (业务处理层)
* abc-business/model (数据访问模型,可转vo,重新生成会被覆盖)
# 引用core包
```xml
abc-dev
abc-core
0.0.1-SNAPSHOT
```
# 框架统一预留注解
```java
@Permission //权限认证, 应用范围:类、方法
@Pass //跳过认证, 应用范围:方法
@ServiceAccess //服务访问鉴权, 应用范围:类、方法, AccessState默认为DECLINED
@Associative //联动字段,abc.api.associative-query-enable=true时从该注解获取联动查询列表,联动更新为abc.api.associative-update-enable
@PostAction //BaseController查询数据后传入BLL的方法
@PreAction //BaseController更新数据前传入BLL的方法
@GetByIdAction //根据ID获取数据, 应用范围:方法
@QueryPageAction //查询分页数据, 应用范围:方法
@QueryAction //查询数据, 应用范围:方法
@ExistAction //查询是否存在数据, 应用范围:方法
@CountAction //查询统计数据, 应用范围:方法
@InsertAction //新增数据 (可批量), 应用范围:方法
@UpdateAction //修改数据, 应用范围:方法
@DeleteAction //删除数据, 应用范围:方法
@DeleteBatchAction //批量删除数据, 应用范围:方法
@MinAction //查询指定字段最小值, 应用范围:方法
@MaxAction //查询指定字段最大值, 应用范围:方法
@SumAction //查询指定字段之和, 应用范围:方法
@AvgAction //查询指定字段平均值, 应用范围:方法
@UploadAction //上传文件, 应用范围:方法
@DownloadAction //下载文件, 应用范围:方法
@PlayVideoAction //播放视频, 应用范围:方法
@ExportAction //导出数据, 应用范围:方法
```
# application.yml (ABC配置说明)
```yaml
abc:
md5:
publicKey: abc # MD5加密公钥
api: # API相关配置
associative-query-enable: true # 是否开启联动查询,若开启框架执行DataExecuor的toVo()时会查询@Associative指定的子表数据
associative-update-enable: true # 是否开启联动更新,若开启框架执行DataExecuor的更新方法时会提交数据@Associative指定的子表,外键由Associative注解指定
keyword-search-enable: true # 是否开启关键词查询功能,若开启则支持分隔符处理OR条件,分隔符默认为空格
keyword-splitter: ; # 关键词查询分隔符,默认为空格
keyword-search-el-expression-open: true # 是否开启关键词查询EL表达式,若开启则DataExecuor查询数据时支持字段值填写EL表达式,
validation-enable: true # 是否开启校验器
#校验器配置规则
validation:
- name: mobile #提交参数名
pattern: 1[3578][0-9]{9} #匹配正则表达式
message: 请输入正确手机号 #未匹配的提示消息
require: true #是否必填
methods: post,put #请求方式,多请求方式以逗号分隔
```
# El表达式查询条件
### 默认查询接口支持El表达式,表达式写法如下所示:
* IN查询: ${1,2,3,4,5,...}
```json
{
"id: "${1,2,3,4,5}"
}
```
* 数字范围值查询: ${1-3,5-7,...}
```json
{
"id: "${1-3,5-7}"
}
```
* 时间范围值查询: ${2022/01/01 00:00:00 - 2022/01/01 01:00:00,...}
注意:时间格式为 yyyy/MM/dd HH:mm:ss-yyyy/MM/dd HH:mm:ss 或 yyyy-MM-dd HH:mm:ss~yyyy-MM-dd HH:mm:ss
```json
{
"create_time: "${2022/01/01 00:00:00 - 2022/01/01 01:00:00}"
}
```
或
```json
{
"create_time: "${2022-01-01 00:00:00 ~ 2022-01-01 01:00:00}"
}
```
* 高级查询: 高级查询为特殊查询字段,字段名必须为$ADVANCE_SEARCH,支持多字段如:$ADVANCE_SEARCH、$ADVANCE_SEARCH_1、$ADVANCE_SEARCH_2,以下划线加下标表示,以此类推;多字段间表达式将转换成AND条件查询,表达式如:${name like 字典,component=zjlj},表达式之间以逗号分隔并将转换为OR条件,表达式操作符支持>、=、<、>=、<=、 like
```json
{
"$ADVANCE_SEARCH": "${name like 字典,component=zjlj}",
"$ADVANCE_SEARCH_1": "${name like 字典,component=kk}"
}
```
# SpringDoc文档地址
### http://{domain}:{port}/doc.html
* 例:http://localhost:8080/doc.html
# Swagger文档地址
### http://{domain}:{port}/swagger-ui.html
* 例:http://localhost:8080/swagger-ui.html
# 框架默认接口 (BaseController)
### 接口URL请求规则
* URL:http://{domain}:{port}{context}/{datasource}/{project.name}/{table}/{id}
* 请求方式:GET、POST、PUT、DELETE
* URL参数:{table}为数据库表名称,{id}为主键
* 示例:http://127.0.0.1:8088/api/dev/user/1
### URL参数说明
* 注意:表名采用下划线连接的在URL参数中应去掉下划线作为table参数,如:user_info->userinfo
```yaml
domain: 域名或IP
port: 端口
context: 前辍,如:/api
project.name: 生成代码时填入的项目名称
datasource: 数据源名称
table: 表名
id: 主键
field: 字段名
index: 文件索引, 如上传多张图片到某个字段中, 取第0个文件
```
### 返回参数说明
```javascript
{
"status": 0, //状态码
"message": "OK", //消息
"data": { //返回数据
"1573228912416047106": true
},
"pageInfo": null, //分页对象
"uuid": null //当前请求UUID
}
```
### 默认接口清单:
注:默认接口清单中的URL一律以相对路径表示,实际请求时请自行拼接上http://{domain}:{port}{context}/{datasource}/{project.name}
#### 1. 新增数据 (可批量)
* URL:/{table}/insert
* 请求方式:PUT
* 提交参数:
```json
[
{
"{field}": "值",
...
}
...
]
```
* 返回:
```json
{
"status": 0,
"message": "OK",
"data": {
"1573228912416047106": true
},
"pageInfo": null,
"uuid": null
}
```
#### 2. 删除数据
* URL:/{table}/{id}
* 请求方式:DELETE
#### 3. 批量删除数据
* URL:/{table}/delete
* 请求方式:DELETE
* 提交参数({id}数组):
```json
[
1,
2,
3,
...
]
```
#### 4. 修改数据
* URL:/{table}/{id}
* 请求方式:PUT
* 提交参数:
```json
{
"${field}": "值",
...
}
```
#### 5. 查询数据
* URL:/{table}/query
* 请求方式:POST
* 查询参数:
```json
{
"${field}": "值",
...
}
```
* 排序查询参数$ORDERS,该字段是特殊查询字段,用于查询排序
```json
{
"$ORDERS": "[{\"column\":\"id\", \"asc\":\"false\"}]"
}
```
#### 6. 分页查询
* URL:/{table}/query/{pageIndex}/{pageSize}
* pageIndex: 当前页码
* pageSize:每页显示多少条
* 请求方式:POST
* 查询参数:
```json
{
"${field}": "值",
...
}
```
* 排序查询参数$ORDERS,该字段是特殊查询字段,用于查询排序
```json
{
"$ORDERS": "[{\"column\":\"id\", \"asc\":\"false\"}]"
}
```
#### 7. 查询总数
* URL:/{table}/count
* 请求方式:POST
* URL参数:{table}为数据库表名称
* 查询参数:
```json
{
"${field}": "值",
...
}
```
#### 8. 查询是否存在数据
* URL:/{table}/exist
* 请求方式:POST
* 查询参数:
```json
{
"${field}": "值",
...
}
```
#### 9. 根据ID获取数据
* URL:/{table}/{id}
* 请求方式:GET
#### 10. 下载文件
* URL:/{datasource}/download/{table}/download/{field}/{id}/{index}
* 请求方式:GET
#### 11. 上传文件
* URL:/{table}/upload
或
* URL:/{table}/{field}/upload/{id}
* 请求方式:POST
* 表单参数:
```text
表单:
file: 文件1
file: 文件2
file: 文件3
...
```
#### 12. 视频播放
* URL:/{table}/play/{field}/{id}/{index}
* 请求方式:GET
#### 13. 导出Excel
* URL:/{table}/export
* 请求方式:POST
* 查询参数:
```json
{
"${field}": "值",
...
}
```
#### 14. 导入Excel
* URL:/{table}/import
* 请求方式:POST
* 表单参数:
```text
表单:
file: 文件1
file: 文件2
file: 文件3
```
* Excel文件格式:
1. 需要导入的excel文件中新增一个名称为schema的sheet
2. schema的第一行为需要导入的表格表头
3. schema的第二行为对应数据库的字段名
#### 15. 统计字段最小值
* URL:/{table}/min/{field}
* 请求方式:POST
* 查询参数:
```json
{
"${field}": "值",
...
}
```
#### 16. 统计字段最大值
* URL:/{table}/max/{field}
* 请求方式:POST
* 查询参数:
```json
{
"${field}": "值",
...
}
```
#### 17. 统计字段之和
* URL:/{table}/sum/{field}
* 请求方式:POST
* 查询参数:
```json
{
"${field}": "值",
...
}
```
#### 18. 统计字段平均值
* URL:/{table}/avg/{field}
* 请求方式:POST
* 查询参数:
```json
{
"${field}": "值",
...
}
```
#### 19. 插入表单数据(支持上传文件)
* URL:/{table}/form/insert
* 请求方式:POST
* 表单参数:
```text
表单:
字段1: 文件|字符串
字段2: 文件|字符串
字段3: 文件|字符串
```
# 自定义ISqlInjector
* 可以通过实现ISqlInjector注册到springboot的bean工厂实现自定义的mybatis-plus默认方法
# 自定义sql拦截器
* 可以通过实现InnerInterceptor注册到springboot自定义的sql拦截器
```java
@Component
@Qualifier("sqlInterceptor")
public class SqlInterceptor implements InnerInterceptor {
@Override
public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
//step1. 查询是否存在需要构建高级查询的参数 ,如果没有就直接返回
QueryWrapper queryWrapper = (QueryWrapper)((Map)parameter).get("ew");
// 没有参数直接返回
if (null == queryWrapper) {
return;
}
//step2. 获取到当前需要执行的sql
String buildSql = boundSql.getSql();
//step3. 拼接查询条件
buildSql = String.format("select temp.* from ( %s ) as temp where %s ", buildSql, "1=1");
//step4. 修改完成的sql 再设置回去
PluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);
mpBoundSql.sql(buildSql);
}
}
```
* 也可以通过实现并注入ISqlInterceptorFactory工厂类返回注册多个sql拦截器
```java
@Primary
@Component
@Qualifier("sqlInterceptorFactory")
public class QueryInterceptorFactory implements ISqlInterceptorFactory {
@Override
public List getInterceptors() {
return new ArrayList<>() {{
add(new QueryInterceptor());
}};
}
@Override
public void add(T interceptor) {
}
}
```
# 上传下载、导入、导出自定义响应
* IUploadHandler,重写该接口并注册Bean可在默认上传接口自定义上传下载实现
#### 示例
```java
@Component
public class MyUpload implements IUploadHandler {
/***
* 上传文件
* @param basePath 相对路径
* @param filename 文件名
* @param data 文件流
* @return
* @throws Exception
*/
@Override
public String upload(String basePath, String filename, byte[] data) {
return null;
}
/***
* 下载文件
* @param uploadPath 文件上传路径
* @return 文件流
* @throws Exception
*/
@Override
public byte[] download(String uploadPath) {
return new byte[0];
}
}
```
* IDataImportHandler,重写该接口并注册Bean可在默认导入接口接收文件流自定义导入数据
#### 示例
```java
@Component
public class MyDataImport implements IDataImportHandler {
/***
* 导入excel数据
* @param dataBuffer excel文件数据流
* @return
* @throws Exception
*/
@Override
public void call(String dbname, String table, T model, DataBuffer dataBuffer) {
}
}
```
* IDataImportHandler,重写该接口并注册Bean可在默认导入接口接收文件流自定义导入数据
#### 示例
```java
@Component
public class MyDataExport implements IDataExportHandler {
/***
* 导出数据
* @param modelList model列表
* @return
* @throws Exception
*/
@Override
byte[] call(List modelList);
}
}
```
# 自定义SqlHelper
* 可以通过自定义SqlHelper来兼容多种类数据库,需实现ISqlHelper接口的getTableList、getColumnList两个方法,Qualifier可指定的值为:MysqlHelper、OracleHelper、SqLiteHelper、SqlServerHelper、PostgresqlHelper、DB2Helper、SybaseHelper
```java
@Component
@Qualifier("sqlServerHelper")
public class DefaultSqlHelper implements ISqlHelper{
@Override
public List