This commit is contained in:
yubaolee
2024-06-19 20:32:29 +08:00
19 changed files with 239 additions and 110 deletions

View File

@@ -175,7 +175,7 @@ namespace Infrastructure
{
if (!string.IsNullOrEmpty(filterjson))
{
var filterGroup = JsonHelper.Instance.Deserialize<FilterGroup>(filterjson);
var filterGroup = JsonHelper.Instance.Deserialize<QueryObject>(filterjson);
query = GenerateFilter(query, parametername, filterGroup);
}
@@ -186,7 +186,7 @@ namespace Infrastructure
{
if (!string.IsNullOrEmpty(filterjson))
{
var filterGroup = JsonHelper.Instance.Deserialize<FilterGroup>(filterjson);
var filterGroup = JsonHelper.Instance.Deserialize<QueryObject>(filterjson);
query = GenerateFilter(query, parametername, filterGroup);
}
@@ -199,13 +199,13 @@ namespace Infrastructure
/// <typeparam name="T"></typeparam>
/// <param name="query"></param>
/// <param name="parametername"></param>
/// <param name="filterGroup"></param>
/// <param name="queryObject"></param>
/// <returns></returns>
public static IQueryable<T> GenerateFilter<T>(this IQueryable<T> query, string parametername,
FilterGroup filterGroup)
QueryObject queryObject)
{
var param = CreateLambdaParam<T>(parametername);
Expression result = ConvertGroup<T>(filterGroup, param);
Expression result = ConvertGroup<T>(queryObject, param);
query = query.Where(param.GenerateTypeLambda<T>(result));
return query;
}
@@ -215,14 +215,14 @@ namespace Infrastructure
/// </summary>
/// <param name="query"></param>
/// <param name="parametername"></param>
/// <param name="filterGroup"></param>
/// <param name="queryObject"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static ISugarQueryable<T> GenerateFilter<T>(this ISugarQueryable<T> query, string parametername,
FilterGroup filterGroup)
QueryObject queryObject)
{
var param = CreateLambdaParam<T>(parametername);
Expression result = ConvertGroup<T>(filterGroup, param);
Expression result = ConvertGroup<T>(queryObject, param);
query = query.Where(param.GenerateTypeLambda<T>(result));
return query;
}
@@ -230,62 +230,62 @@ namespace Infrastructure
/// <summary>
/// 转换filtergroup为表达式
/// </summary>
/// <param name="filterGroup"></param>
/// <param name="queryObject"></param>
/// <param name="param"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static Expression ConvertGroup<T>(FilterGroup filterGroup, ParameterExpression param)
public static Expression ConvertGroup<T>(QueryObject queryObject, ParameterExpression param)
{
if (filterGroup == null) return null;
if (queryObject == null) return null;
if (filterGroup.Filters.Length == 1 &&(filterGroup.Children == null || !filterGroup.Children.Any())) //只有一个条件
if (queryObject.Filters.Length == 1 &&(queryObject.Children == null || !queryObject.Children.Any())) //只有一个条件
{
return param.GenerateBody<T>(filterGroup.Filters[0]);
return param.GenerateBody<T>(queryObject.Filters[0]);
}
Expression result = ConvertFilters<T>(filterGroup.Filters, param, filterGroup.Operation);
Expression gresult = ConvertGroup<T>(filterGroup.Children, param, filterGroup.Operation);
Expression result = ConvertFilters<T>(queryObject.Filters, param, queryObject.Operation);
Expression gresult = ConvertGroup<T>(queryObject.Children, param, queryObject.Operation);
if (gresult == null) return result;
if (result == null) return gresult;
if (filterGroup.Operation == "and")
if (queryObject.Operation == "and")
{
return result.AndAlso(gresult);
}
else //or
{
return result.Or(gresult);
return Expression.OrElse( result, gresult);
}
}
/// <summary>
/// 转换FilterGroup[]为表达式不管FilterGroup里面的Filters
/// </summary>
/// <param name="groups"></param>
/// <param name="queryObjs"></param>
/// <param name="param"></param>
/// <param name="operation"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
private static Expression ConvertGroup<T>(FilterGroup[] groups, ParameterExpression param, string operation)
private static Expression ConvertGroup<T>(QueryObject[] queryObjs, ParameterExpression param, string operation)
{
if (groups == null || !groups.Any()) return null;
if (queryObjs == null || !queryObjs.Any()) return null;
Expression result = ConvertGroup<T>(groups[0], param);
Expression result = ConvertGroup<T>(queryObjs[0], param);
if (groups.Length == 1) return result;
if (queryObjs.Length == 1) return result;
if (operation == "and")
{
foreach (var filter in groups.Skip(1))
foreach (var filter in queryObjs.Skip(1))
{
result = result.AndAlso(ConvertGroup<T>(filter, param));
}
}
else
{
foreach (var filter in groups.Skip(1))
foreach (var filter in queryObjs.Skip(1))
{
result = result.Or(ConvertGroup<T>(filter, param));
result = Expression.OrElse(result, ConvertGroup<T>(filter, param));
}
}
@@ -325,7 +325,7 @@ namespace Infrastructure
{
foreach (var filter in filters.Skip(1))
{
result = result.Or(param.GenerateBody<T>(filter));
result = Expression.OrElse(result, param.GenerateBody<T>(filter));
}
}

View File

@@ -1,25 +1,64 @@
namespace Infrastructure
{
/// <summary>
/// 查询表达式中的最小单元,如:
/// new Filter {Key = "name", Value = "yubaolee", Contrast = "=="},
/// new Filter {Key = "name", Value = "yubaolee", Contrast = "contains"},
/// new Filter {Key = "age", Value = "10,20,30", Contrast = "in"},
/// new Filter {Key = "10,20,30", Value = "40", Contrast = "intersect"}
/// </summary>
public class Filter
{
/// <summary>
/// 过滤条件的关键字。
/// </summary>
public string Key { get; set; }
/// <summary>
/// 通常为值yubaolee、10、10,20,30等。
/// </summary>
public string Value { get; set; }
/// <summary>
/// 通常为运算符,如:==、contains、in、intersect等。
/// </summary>
public string Contrast { get; set; }
/// <summary>
/// 对于特殊值的说明
/// </summary>
public string Text { get; set; }
}
public class FilterGroup
/// <summary>
/// 查询对象类,用于封装查询条件。
/// </summary>
public class QueryObject
{
/// <summary>
/// or /and
/// 操作类型定义了查询条件之间的逻辑关系如OR、AND。
/// </summary>
/// <remarks>
/// 该属性决定了如何组合多个过滤条件,以构建复杂的查询逻辑。
/// </remarks>
public string Operation { get; set; }
/// <summary>
/// 过滤器数组,包含一组过滤条件。
/// </summary>
public Filter[] Filters { get; set; }
public FilterGroup[] Children { get; set; }
/// <summary>
/// 子查询对象数组,支持嵌套查询。
/// </summary>
/// <remarks>
/// 通过嵌套查询对象,可以构建复杂的查询逻辑,处理更复杂的数据关系。
/// </remarks>
public QueryObject[] Children { get; set; }
}
}

View File

@@ -0,0 +1,51 @@
using System.Linq;
using System.Text;
namespace Infrastructure;
/// <summary>
/// 查询对象FilterGroup扩展将对象转换为sql查询
/// </summary>
public static class QueryObjExtension
{
/// <summary>
/// 根据过滤器组构建查询。
/// </summary>
public static string BuildQuery(this QueryObject query)
{
StringBuilder sb = new StringBuilder();
BuildQuery(query, sb);
return sb.ToString();
}
/// <summary>
/// 递归构建查询字符串。
/// </summary>
private static void BuildQuery(QueryObject query, StringBuilder sb)
{
// 构建当前过滤器组的过滤条件
if (query.Filters != null && query.Filters.Length > 0)
{
string filters = string.Join($" {query.Operation} ", query.Filters.Select(f => $"{f.Key} {f.Contrast} '{f.Value}'"));
sb.Append($"({filters})");
}
// 构建当前过滤器组的子过滤器组
if (query.Children != null && query.Children.Length > 0)
{
// 如果当前字符串不为空,则添加操作符连接子过滤器组
if (sb.Length > 0)
{
sb.Append($" {query.Operation} ");
}
// 使用递归方式构建子过滤器组的查询字符串,并连接到当前字符串
string children = string.Join($" {query.Operation} ", query.Children.Select(child =>
{
StringBuilder childSb = new StringBuilder();
BuildQuery(child, childSb);
return childSb.ToString();
}));
sb.Append($"({children})");
}
}
}

View File

@@ -9,7 +9,7 @@ namespace Infrastructure.Test
[Test]
public void Convert()
{
FilterGroup sub = new FilterGroup
QueryObject sub = new QueryObject
{
Operation = "or"
};
@@ -19,22 +19,22 @@ namespace Infrastructure.Test
new Filter {Key = "c3", Value = "10,20,30", Contrast = "in"}
};
FilterGroup filterGroup = new FilterGroup
QueryObject queryObject = new QueryObject
{
Operation = "and"
};
filterGroup.Filters = new[]
queryObject.Filters = new[]
{
new Filter {Key = "c1", Value = "name", Contrast = "contains"},
new Filter {Key = "10,20,30", Value = "40", Contrast = "intersect"}
};
filterGroup.Children = new[]
queryObject.Children = new[]
{
sub
};
var expression = DynamicLinq.ConvertGroup<TestOjb>(filterGroup,
var expression = DynamicLinq.ConvertGroup<TestOjb>(queryObject,
Expression.Parameter(typeof(TestOjb), "c"));
Console.WriteLine(expression.ToString());

View File

@@ -63,7 +63,7 @@ namespace OpenAuth.App
string.Join(',',orgs));
}
return UnitWork.Find<T>(null).GenerateFilter(parametername,
JsonHelper.Instance.Deserialize<FilterGroup>(rule.PrivilegeRules));
JsonHelper.Instance.Deserialize<QueryObject>(rule.PrivilegeRules));
}
/// <summary>

View File

@@ -75,7 +75,7 @@ namespace OpenAuth.App
string.Join(',',orgs));
}
return SugarClient.Queryable<T>().GenerateFilter(parametername,
JsonHelper.Instance.Deserialize<FilterGroup>(rule.PrivilegeRules));
JsonHelper.Instance.Deserialize<QueryObject>(rule.PrivilegeRules));
}
/// <summary>

View File

@@ -26,6 +26,7 @@ using System.Linq.Expressions;
using System.Net.Http;
using System.Threading.Tasks;
using Infrastructure.Const;
using Infrastructure.Extensions;
using Infrastructure.Helpers;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json.Linq;
@@ -136,8 +137,16 @@ namespace OpenAuth.App
{
var t = Type.GetType("OpenAuth.App." + flowInstance.DbName + "App");
ICustomerForm icf = (ICustomerForm) _serviceProvider.GetService(t);
try
{
icf.Add(flowInstance.Id, flowInstance.FrmData);
}
catch (Exception e)
{
throw new Exception("流程表单数据解析失败,请检查表单是否填写完整");
}
}
//如果工作流配置的表单配置有对应的数据库
if (!string.IsNullOrEmpty(form.DbName))
@@ -616,7 +625,10 @@ namespace OpenAuth.App
//当审批流程时,能进到这里,表明当前登录用户已经有审批当前节点的权限,完全可以直接用登录用户的直接上级
var user = _auth.GetCurrentUser().User;
var parentId = _userManagerApp.GetParent(user.Id);
if (StringExtension.IsNullOrEmpty(parentId))
{
throw new Exception("无法找到当前用户的直属上级");
}
makerList = GenericHelpers.ArrayToString(new[]{parentId}, makerList);
}
else if (wfruntime.nextNode.setInfo.NodeDesignate == Setinfo.RUNTIME_CHAIRMAN)
@@ -825,7 +837,7 @@ namespace OpenAuth.App
// 加入搜索自定义标题
if (!string.IsNullOrEmpty(request.key))
{
waitExp = waitExp.And(t => t.CustomName.Contains(request.key));
waitExp = PredicateBuilder.And(waitExp, t => t.CustomName.Contains(request.key));
}
result.count = await UnitWork.Find(waitExp).CountAsync();
@@ -859,7 +871,7 @@ namespace OpenAuth.App
// 加入搜索自定义标题
if (!string.IsNullOrEmpty(request.key))
{
myFlowExp = myFlowExp.And(t => t.CustomName.Contains(request.key));
myFlowExp = PredicateBuilder.And(myFlowExp, t => t.CustomName.Contains(request.key));
}
result.count = await UnitWork.Find(myFlowExp).CountAsync();

View File

@@ -18,7 +18,7 @@ namespace OpenAuth.App.Test
var services = new ServiceCollection();
var cachemock = new Mock<ICacheContext>();
cachemock.Setup(x => x.Get<UserAuthSession>("tokentest")).Returns(new UserAuthSession { Account = "Systems" });
cachemock.Setup(x => x.Get<UserAuthSession>("tokentest")).Returns(new UserAuthSession { Account = "admin" });
services.AddScoped(x => cachemock.Object);
var httpContextAccessorMock = new Mock<IHttpContextAccessor>();
@@ -37,7 +37,7 @@ namespace OpenAuth.App.Test
{
var app = _autofacServiceProvider.GetService<ResourceApp>();
var result = app.Load(new QueryResourcesReq());
Console.WriteLine(JsonHelper.Instance.Serialize(result));
Console.WriteLine(JsonHelper.Instance.Serialize(result.Result));
}
[Test]
@@ -46,7 +46,7 @@ namespace OpenAuth.App.Test
var auth = _autofacServiceProvider.GetService<IAuth>();
var app = _autofacServiceProvider.GetService<DataPrivilegeRuleApp>();
//该测试解析为:针对资源列表,【管理员】可以看到所有,角色为【神】或【测试】的只能看到自己创建的
var filterGroup = new FilterGroup
var filterGroup = new QueryObject
{
Operation = "or"
};
@@ -61,7 +61,7 @@ namespace OpenAuth.App.Test
};
filterGroup.Children = new[]
{
new FilterGroup //登录用户角色包含【测试】或包含【神】的,只能看到自己的
new QueryObject //登录用户角色包含【测试】或包含【神】的,只能看到自己的
{
Operation = "and",
Filters = new Filter[]

View File

@@ -62,7 +62,7 @@
var _hmt = _hmt || [];
(function () {
var hm = document.createElement("script");
hm.src = "//hm.baidu.com/hm.js?0558502420ce5fee054b31425e77ffa6";
hm.src = "//hm.baidu.com/hm.js?93a7b9a145222f9b7109d643a0c58f8d";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -44,7 +44,7 @@ layui.config({
var _hmt = _hmt || [];
(function () {
var hm = document.createElement("script");
hm.src = "//hm.baidu.com/hm.js?0558502420ce5fee054b31425e77ffa6";
hm.src = "https://hm.baidu.com/hm.js?93a7b9a145222f9b7109d643a0c58f8d";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();

View File

@@ -94,7 +94,7 @@ namespace OpenAuth.Repository.Domain
/// 前端界面是否缓存
/// </summary>
/// [Description("前端界面是否缓存")]
public bool KeepAlive { get; set; }
public bool? KeepAlive { get; set; }
}
}

View File

@@ -59,7 +59,7 @@ namespace OpenAuth.Repository.Test
[Test]
public void TestDynamic()
{
FilterGroup sub = new FilterGroup
QueryObject sub = new QueryObject
{
Operation = "or"
};
@@ -69,24 +69,24 @@ namespace OpenAuth.Repository.Test
new Filter {Key = "Sex", Value = "10", Contrast = "=="}
};
FilterGroup filterGroup = new FilterGroup
QueryObject queryObject = new QueryObject
{
Operation = "and"
};
filterGroup.Filters = new[]
queryObject.Filters = new[]
{
new Filter {Key = "Account", Value = "name", Contrast = "=="},
new Filter {Key = "Password", Value = "10", Contrast = "=="}
};
filterGroup.Children = new[]
queryObject.Children = new[]
{
sub
};
var dbcontext = _autofacServiceProvider.GetService<OpenAuthDBContext>();
var query = dbcontext.Users.GenerateFilter("c",JsonHelper.Instance.Serialize(filterGroup));
var query = dbcontext.Users.GenerateFilter("c",JsonHelper.Instance.Serialize(queryObject));
Console.WriteLine(query.Expression.ToString());
}
}

View File

@@ -73,7 +73,7 @@ namespace OpenAuth.Repository.Test
[Test]
public void TestDynamic()
{
FilterGroup sub = new FilterGroup
QueryObject sub = new QueryObject
{
Operation = "or"
};
@@ -83,24 +83,24 @@ namespace OpenAuth.Repository.Test
new Filter {Key = "Sex", Value = "10", Contrast = "=="}
};
FilterGroup filterGroup = new FilterGroup
QueryObject queryObject = new QueryObject
{
Operation = "and"
};
filterGroup.Filters = new[]
queryObject.Filters = new[]
{
new Filter {Key = "Account", Value = "name", Contrast = "=="},
new Filter {Key = "Password", Value = "10", Contrast = "=="}
};
filterGroup.Children = new[]
queryObject.Children = new[]
{
sub
};
var sugarClient = _autofacServiceProvider.GetService<ISqlSugarClient>();
var query = sugarClient.Queryable<User>().GenerateFilter("c",filterGroup);
var query = sugarClient.Queryable<User>().GenerateFilter("c",queryObject);
Console.WriteLine(query.ToSqlString());
}
}

View File

@@ -1,18 +1,5 @@
🔥.Net权限管理及快速开发框架、最好用的权限工作流系统。源于Martin Fowler企业级应用开发思想及最新技术组合SqlSugar、EF、Quartz、AutoFac、WebAPI、Swagger、Mock、NUnit、Vue2/3、Element-ui/plus、IdentityServer等。核心模块包括角色授权、代码生成、智能打印、表单设计、工作流、定时任务等。架构易扩展是中小企业的首选。
## ❤❤❤郑重声明❤❤❤
主分支main运行环境默认为.Net SDK 6.0如果你使用vs2019作为开发工具请注意查看[VS2019打开6.0及以后版本](http://doc.openauth.net.cn/core/faq.html#vs2019%E6%89%93%E5%BC%806-0%E5%8F%8A%E4%BB%A5%E5%90%8E%E7%89%88%E6%9C%AC)
需要.Net SDK 4.0/4.5开发环境的同学请查看本项目4.0分支,已停止维护
使用.Net Core 2.1--3.1的请看:
**GitHub** https://github.com/yubaolee/OpenAuth.Core
**码云** https://gitee.com/yubaolee/OpenAuth.Core
![LOGO](https://gitee.com/uploads/images/2018/0425/163228_7077c3fd_362401.png "1.png")
**logo图标含义** OpenAuth中OA字母的结合体整体像鱼授人以渔你非说像咸鱼那也是积极向上的咸鱼中心是个笑脸微笑面对生活(✿◡‿◡)。
@@ -21,7 +8,6 @@
**官方文档** http://doc.openauth.net.cn
![](https://img.shields.io/badge/release-6.0-blue)
![](https://img.shields.io/badge/SqlSugar-5.1.4-blue)
![](https://img.shields.io/badge/IdentityServer4-3.0.1-blue)
@@ -41,6 +27,23 @@
![](https://img.shields.io/badge/npm-9.7.1-brightgreen)
![](https://img.shields.io/badge/layui-2.8.6-brightgreen)
## ❤❤❤郑重声明❤❤❤
主分支main运行环境默认为.Net SDK 6.0,支持.NET未来版本需要.Net SDK 4.0/4.5开发环境的同学请查看本项目4.0分支,已停止维护
使用.Net Core 2.1-3.1的请进https://gitee.com/yubaolee/OpenAuth.Core ,已停止维护
## OpenAuth.Net系列视频火热更新中
[OpenAuth.Net视频合集--系统结构及代码下载](https://www.bilibili.com/video/BV1Z1421q7xU/)
[OpenAuth.Net视频合集--基于RBAC体系的权限管理介绍](https://www.bilibili.com/video/BV1M9KeejENf/)
[OpenAuth.Net视频合集--企业版代码启动](https://www.bilibili.com/video/BV1KSuQebEek/)
[OpenAuth.Net视频合集--使用企业版代码生成器](https://www.bilibili.com/video/BV1JCuyeaEFp/)
[OpenAuth.Net视频合集--表单设计](https://www.bilibili.com/video/BV1dagEeFEVA/)
## 关于OpenAuth.Net企业版/高级版的说明:

View File

@@ -39,11 +39,18 @@ CodeSmith Generator Studio 8.0或以上
如下图使用CodeSmith文件夹中的模板右击【ApiGenerate.cst】--【Execute】选择需要生成的表本文以Stock为例及相关的上下文命名空间点击【Generate】
![](http://pj.openauth.net.cn/zentao/file-read-26.jpg)
![20240613220037](http://img.openauth.net.cn/20240613220037.png)
注意,有两个配置项:
* WholeDb: 如果选中,则按数据库中所有表生成实体及逻辑;否则,按选择的表生成
* HeaderModel会生成主、从表结构类似 WmsInboundOrderTbl / WmsInboundOrderDtbl
生成成功后在CodeSmith/Csharp文件夹下面会有Stock实体相关文档如下图
![](http://pj.openauth.net.cn/zentao/file-read-53.png)
![20240613220224](http://img.openauth.net.cn/20240613220224.png)
把CSharp\OpenAuth.App覆盖到自己项目对应目录
@@ -51,7 +58,6 @@ CodeSmith Generator Studio 8.0或以上
**把CSharp\OpenAuth.Repository\OpenAuthDBContext.cs中的内容添加到自己项目的文件中千万不要直接覆盖文件**
**其他文件夹的内容为WebAPI项目使用可以不管。**
## 添加界面
@@ -69,7 +75,15 @@ userJs直接覆盖到OpenAuth.Mvc/wwwroot中
## 添加模块
编写完上面代码后运行系统使用System账号登录系统在【模块管理】中添加`仓储管理`模块,并为它添加菜单这里我只添加一个菜单【btnAdd】如下图
编写完上面代码后运行系统使用System账号登录系统在【模块管理】中添加`仓储管理`模块,
![20240613220434](http://img.openauth.net.cn/20240613220434.png)
::: warning 注意
因为生成的Controller名称类似XXXsController所以模块的Url地址应该是XXXs/Index
:::
并为它添加菜单这里我只添加一个菜单【btnAdd】如下图
![](http://pj.openauth.net.cn/zentao/file-read-51.png)

View File

@@ -10,9 +10,13 @@ OpenAuth.Pro是一套全新的前端界面基于vue-element-admin采用VUE
## OpenAuth.Net系列教学视频
[OpenAuth.Net教学合集--系统结构及代码下载](https://www.bilibili.com/video/BV1Z1421q7xU/)
[OpenAuth.Net视频合集--系统结构及代码下载](https://www.bilibili.com/video/BV1Z1421q7xU/)
[OpenAuth.Net教学合集--企业版代码启动](https://www.bilibili.com/video/BV1KSuQebEek/)
[OpenAuth.Net视频合集--企业版代码启动](https://www.bilibili.com/video/BV1KSuQebEek/)
[OpenAuth.Net视频合集--使用企业版代码生成器](https://www.bilibili.com/video/BV1JCuyeaEFp/)
[OpenAuth.Net视频合集--权限管理介绍](https://www.bilibili.com/video/BV1M9KeejENf/)
## 工具准备

View File

@@ -1,5 +1,7 @@
# 企业版代码生成器
本章节视频讲解请参考:[OpenAuth.Net视频合集--使用企业版代码生成器](https://www.bilibili.com/video/BV1JCuyeaEFp/)
## 术语解释
在添加新功能之前需要先了解OpenAuth.Net生成代码时的两个概念动态头部和固定头部
@@ -49,17 +51,21 @@ initCfg() {
-- mysql示例
create table stock
(
createtime datetime not null comment '操作时间',
status int not null comment '出库/入库',
price decimal(10, 1) not null comment '产品单价',
number int not null comment '产品数量',
name text not null comment '产品名称',
orgid varchar(50) null comment '组织ID',
user varchar(50) not null comment '操作人',
viewable varchar(50) not null comment '可见范围',
id varchar(50) not null comment '数据ID'
primary key
Id varchar(50) not null comment '数据ID'
primary key,
Name text not null comment '产品名称',
Number int not null comment '产品数量',
Price decimal(10, 1) not null comment '产品单价',
Status int not null comment '出库/入库',
Viewable varchar(50) not null comment '可见范围',
User varchar(50) not null comment '操作人',
Time datetime not null comment '操作时间',
OrgId varchar(50) null comment '组织ID'
)
comment '出入库信息表' charset = utf8
row_format = COMPACT;
```