企业级自定义表单引擎解决方案(十)--缓存设计2
Python微信订餐小程序课程视频
https://edu.csdn.net/course/detail/36074
Python实战量化交易理财系统
https://edu.csdn.net/course/detail/35475
新年伊始,万物皆生机,然冠未去,美帝相向,于华夏之子,吾辈当自强。
这篇文章接上一篇文章,主要介绍缓存的代码实现
后端本地缓存
之前介绍的将自定义表单数据全部存储到应用程序内存中,任何自定义表单数据更新之后,都刷新内存缓存,分布式部署涉及到缓存同步刷新问题。
- 全局本地缓存容器设计
- 用线程安全的字典ConcurrentDictionary CacheDict,存储每一个数据对象集合,比如视图集合、表单集合等,每一次数据变更都清除具体的一个字典项数据
- 绝大多数时间都是读取缓存内容,因此这里上的读写锁,读写每一项缓存时,都上自己的读锁,锁的集合存储在ConcurrentDictionary CacheReaderWriterLockDict变量中,Key与CacheDict的Key相同。
- 当检测到缓存通知服务断开时,会将本地所有缓存清空,直接读取原始数据库,用bool IsEnabledLocalCache变量控制。
- 当读取缓存时,发现本地缓存没有数据,则调用具体加载数据委托方法,本地没有数据读取时,需要加锁,防止缓存穿透。
具体代码如下:
?
| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586 | ///
/// 本地缓存容器``///
public
class
LocalCacheContainer``{``private
static
ConcurrentDictionary<``string``,
object``> CacheDict;``private
static
ConcurrentDictionary<``string``, ReaderWriterLock> CacheReaderWriterLockDict;
static
LocalCacheContainer()``{``CacheDict =
new
ConcurrentDictionary<``string``,
object``>();``CacheReaderWriterLockDict =
new
ConcurrentDictionary<``string``, ReaderWriterLock>();``}
public
static
bool
IsEnabledLocalCache {
get``;
private
set``; } =
true``;
///
/// 缓存通知断开时调用``///
/// 是否启用缓存``internal
static
void
SetLocalCacheIsEnabled(``bool
isEnabled)``{``IsEnabledLocalCache = isEnabled;``if``(!isEnabled)``{``ClearAllCache();``}``}
public
static
object
Get(``string
key, Func<``string``,
object``> factory)``{``var
readerWriterLock = GetReadWriteLock(key);``readerWriterLock.AcquireReaderLock(5000);
try``{``//return CacheDict.GetOrAdd(key, factory); // 缓存穿透?``if
(CacheDict.ContainsKey(key))``{``return
CacheDict.GetOrAdd(key, factory);``}``else``{``lock
(``string``.Intern(key))``{``return
CacheDict.GetOrAdd(key, factory);``}``}``}``finally``{``readerWriterLock.ReleaseReaderLock();``}``}
internal
static
void
ClearCache(``string
key)``{``var
readerWriterLock = GetReadWriteLock(key);``readerWriterLock.AcquireWriterLock(5000);
try``{``object
objRemove;``CacheDict.TryRemove(key,
out
objRemove);``}``finally``{``readerWriterLock.ReleaseReaderLock();``}``}
// 清楚所有缓存信息``private
static
void
ClearAllCache()``{``CacheDict.Clear();``CacheReaderWriterLockDict.Clear();``}
private
static
ReaderWriterLock GetReadWriteLock(``string
key)``{``return
CacheReaderWriterLockDict.GetOrAdd(key, k =>``{``return
new
ReaderWriterLock();``});``}``}
|
缓存变更处理
- 主要分为缓存变更通知与接收缓存变更处理,缓存变更只需要通知哪一个Key过期即可。
- 接收缓存变更处理比较简单,接收到缓存变更之后,将内存容器中对应的字典项删除即可。
- 缓存通知定义为接口,如果是单应用部署,直接调用删除本地缓存服务即可,如果是分布式部署,也会调用删除本地缓存数据,通知发送分布式通知到其他自定义表单应用服务器,其他自定义表单应用服务器接收到缓存变更通知时,删除本地缓存数据。
- ReceiveCacheNotice代码
?
| 1234567891011121314151617181920 | public
static
class
ReceiveCacheNotice``{``public
static
void
ReceiveClearCache(``string
key)``{``LocalCacheContainer.ClearCache(key);``}
public
static
void
ReceiveClearCaches(List<``string``> keys)``{``foreach``(``var
key
in
keys)``{``LocalCacheContainer.ClearCache(key);``}``}
public
static
void
SetLocalCacheIsEnabled(``bool
isEnabled)``{``LocalCacheContainer.SetLocalCacheIsEnabled(isEnabled);``}``}
|
- ICacheSendNotice及本地通知LocalCacheSendNotice代码
?
| 123456789101112131415161718192021222324252627282930313233 | ///
/// 设计时实体变更通知缓存``///
public
interface
ICacheSendNotice``{``///
/// 发送缓存变更``///
/// 缓存Key``void
SendClearCache(``string
key);
///
/// 发送缓存多个变更``///
/// 缓存Key集合``void
SendClearCaches(List<``string``> keys);``}
///
/// 本地缓存容器通知服务``///
public
class
LocalCacheSendNotice : ICacheSendNotice``{``public
void
SendClearCache(``string
key)``{``ReceiveCacheNotice.ReceiveClearCache(key);``}
public
void
SendClearCaches(List<``string``> keys)``{``ReceiveCacheNotice.ReceiveClearCaches(keys);``}``}
|
- 分布式缓存发布订阅Redis实现,主要是用StackExchange.Redis组件实现,代码没有太多的逻辑,阅读代码即可。
?
| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475 | ///
/// Redis缓存容器通知服务``///
public
class
RedisCacheSendNotice : ICacheSendNotice``{``private
readonly
SpriteConfig _callHttpConfig;``private
readonly
IDistributedCache _distributedCache;``private
readonly
ISubscriber _subscriber;
public
RedisCacheSendNotice(IDistributedCache distributedCache, IOptions callHttpConfig)``{``_distributedCache = distributedCache;``_callHttpConfig = callHttpConfig.Value;``var
spriteRedisCache = _distributedCache
as
SpriteRedisCache;``spriteRedisCache.RedisDatabase.Multiplexer.ConnectionFailed += Multiplexer_ConnectionFailed;``spriteRedisCache.RedisDatabase.Multiplexer.ConnectionRestored += Multiplexer_ConnectionRestored;``_subscriber = spriteRedisCache.RedisDatabase.Multiplexer.GetSubscriber();
if
(_callHttpConfig.RemoteReceivePreKey !=
null``)``{``foreach
(``var
remoteReceivePreKey
in
_callHttpConfig.RemoteReceivePreKey)``{``_subscriber.Subscribe(remoteReceivePreKey, (channel, message) =>``{``ReceiveCacheNotice.ReceiveClearCache(message);``});
_subscriber.Subscribe($``"{remoteReceivePreKey}s"``, (channel, message) =>``{``List<``string``> keys = JsonConvert.DeserializeObject
string>>(message);```ReceiveCacheNotice.ReceiveClearCaches(keys);
});}
}}` `private` `void` `Multiplexer_ConnectionRestored(
objectsender, StackExchange.Redis.ConnectionFailedEventArgs e){
ReceiveCacheNotice.SetLocalCacheIsEnabled(true
);}` `private` `void` `Multiplexer_ConnectionFailed(
objectsender, StackExchange.Redis.ConnectionFailedEventArgs e){
ReceiveCacheNotice.SetLocalCacheIsEnabled(false
);}` `public` `void` `SendClearCache(
stringkey){
ReceiveCacheNotice.ReceiveClearCache(key);if` `(_callHttpConfig.RemoteNoticePreKey !=` `null
){
if(_callHttpConfig.RemoteNoticePreKey.Any(r => key.StartsWith(KaTeX parse error: Expected 'EOF', got '}' at position 72: …[0], key);``}``}̲``}` `public` `…"{groupKeyList.Key}s"
, JsonConvert.SerializeObject(groupKeyList.ToList()));}
}}
}``}` |
- 具体缓存代码实现举例(以表单为例)
?
| 123456789101112131415161718192021222324252627 | public
class
SpriteFormLocalCache : LocalCache``{``public
override
string
CacheKey => CommonConsts.SpriteFormCacheKey;
public
override
Dictionary GetAllDict(``string
applicationCode)``{``if
(!LocalCacheContainer.IsEnabledLocalCache)
// 如果缓存通知服务不可以,直接读取数据库``{``return
_serviceProvider.DoDapperService(DefaultDbConfig, (unitOfWork) =>``{``return
GetSpriteFormVueDtos(applicationCode, unitOfWork);``});``}``else``{``// 读取本地缓存内容,如果本地缓存没有数据,读取数据库数据,并写入本地缓存容器``return
(Dictionary)LocalCacheContainer.Get($``"{CommonConsts.SpriteFormCachePreKey}-{applicationCode}_{CacheKey}"``, key =>``{``return
_serviceProvider.DoDapperService(DefaultDbConfig, (unitOfWork) =>``{``return
GetSpriteFormVueDtos(applicationCode, unitOfWork);``});``});``}``}``......``}
|
- 前端缓存主要是用IndexDb实现,前端代码暂时没开源,阅读一下即可
?
| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 | import
Dexie from
'dexie'``import
{ SpriteRumtimeApi } from
'@/sprite/api/spriteform'
const db =
new
Dexie(``'formDb'``)``db.version(1).stores({``form:
id})` `db.version(1).stores({``view: `id
})db.version(1).stores({frameworkCache: `id```})` `db.version(1).stores({
dict: id```})
window.spriteDb = db
db.menuFormRelationInfo = {}``const createMenuFormRelations =
function
(routeName, applicationCode, relationInfos) {``if
(!db.menuFormRelationInfo.hasOwnProperty(routeName)) {``db.menuFormRelationInfo[routeName] = {}``db.menuFormRelationInfo[routeName].applicationCode = applicationCode``db.menuFormRelationInfo[routeName].relationInfos = relationInfos``}
else
{``relationInfos.forEach(relationInfo => {``if
(!db.menuFormRelationInfo[routeName].relationInfos.find(r => r.relationType === relationInfo.relationType && r.id === relationInfo.id && r.version === relationInfo.version)) {``db.menuFormRelationInfo[routeName].relationInfos.push(relationInfo)``}
});``}``}
/**``* 递归获取表单或视图关联表单视图版本信息``* @param {guid} objId 表单或视图Id``* @param {int} relationType 1=表单,2=视图``* @param {obj} relationInfos 表单和视图版本信息``*/``const findRelationConfigs = async
function
(objId, relationType, relationInfos) {``if
(!relationInfos) {``relationInfos = []``}``console.log(relationType)``var
findData = relationType === 1 ? await db.form.get(objId) : await db.view.get(objId)``if
(findData && relationInfos.findIndex(r => r.id === findData.id) < 0) {``relationInfos.push({ relationType: relationType, id: findData.id, version: findData.version })``}``if
(findData && findData.relationInfos && findData.relationInfos.length > 0) {``for
(``var
i = 0; i < findData.relationInfos.length; i++) {``await findRelationConfigs(findData.relationInfos[i].id, findData.relationInfos[i].relationType, relationInfos)``}``}``console.log(``'relationInfos'``)``console.log(relationInfos)``return
relationInfos``}
db.getFormData = async
function
(routeName, formId, fromMenu, applicationCode) {``var
formData = await db.form.get(formId)``var
dictFrameworkCache = await db.frameworkCache.get(``'dict'``)``console.log(``"getFormData"``)``if
(!formData) {``var
resultData = await SpriteRumtimeApi.simpleform({ id: formId, applicationCode: applicationCode })``var
menuFormrelationInfos = []``if
(resultData && resultData) {``for
(``var
i = 0; i < resultData.formDatas.length; i++) {``await db.form.put(resultData.formDatas[i])``menuFormrelationInfos.push({relationType: 1, id: resultData.formDatas[i].id, version: resultData.formDatas[i].version})``}``for
(``var
j = 0; j < resultData.viewDatas.length; j++) {``await db.view.put(resultData.viewDatas[j])``menuFormrelationInfos.push({relationType: 2, id: resultData.viewDatas[j].id, version: resultData.viewDatas[j].version})``}``}``if
(resultData && resultData.dictVersion && resultData.dicts) {``await db.frameworkCache.put({ id:
'dict'``, version: resultData.dictVersion })``await db.dict.clear()``await db.dict.bulkAdd(resultData.dicts)``}``createMenuFormRelations(routeName, applicationCode, menuFormrelationInfos)``formData = await db.form.get(formId)``}
else
{
// 从indexdb找到数据,如果从菜单进入,需要调用接口,判断版本号信息``if
(fromMenu) {``delete
db.menuFormRelationInfo[routeName]``var
relationInfos = await findRelationConfigs(formId, 1, [])``var
relationParams = { applicationCode: applicationCode, formId: formId, relationInfos: relationInfos, dictVersion: dictFrameworkCache?.version }``var
checkResult = await SpriteRumtimeApi.checkversions(relationParams)``if
((checkResult && checkResult.formDatas && checkResult.formDatas.length > 0) || (checkResult && checkResult.viewDatas && checkResult.viewDatas.length > 0)) {``relationInfos = []``}``if
(checkResult && checkResult.formDatas && checkResult.formDatas.length > 0) {``for
(``var
i2 = 0; i2 < checkResult.formDatas.length; i2++) {``await db.form.put(checkResult.formDatas[i2])``relationInfos.push({relationType: 1, id: checkResult.formDatas[i2].id, version: checkResult.formDatas[i2].version})``}``}``if
(checkResult && checkResult.viewDatas && checkResult.viewDatas.length > 0) {``for
(``var
j2 = 0; j2 < checkResult.viewDatas.length; j2++) {``await db.view.put(checkResult.viewDatas[j2])``relationInfos.push({relationType: 2, id: checkResult.viewDatas[j2].id, version: checkResult.viewDatas[j2].version})``}``}``if
(checkResult && checkResult.dictVersion && checkResult.dicts) {``await db.frameworkCache.put({ id:
'dict'``, version: checkResult.dictVersion })``await db.dict.clear()``await db.dict.bulkAdd(checkResult.dicts)``}``createMenuFormRelations(routeName, applicationCode, relationInfos)``formData = await db.form.get(formId)``}``}``return
formData``}
|
开源地址:https://gitee.com/kuangqifu/sprite
体验地址:http://47.108.141.193:8031(首次加载可能有点慢,用的阿里云最差的服务器)
自定义表单文章地址:https://blog.csdn.net/spritekuang/
流程引擎文章地址:https://blog.csdn.net/spritekuang/category/834975.html(采用WWF开发,已过时,已改用Elsa实现,https://blog.csdn.net/spritekuang/p/14970992.html )
Github地址:https://github.com/kuangqifu/CK.Sprite.Job
企业级自定义表单引擎解决方案(十)--缓存设计2相关推荐
- 企业级自定义表单引擎解决方案(二)--架构及核心模块设计
.net core研发的自定义表单引擎,采用强大的规则引擎将所有的业务串联起来的,和其他低代码平台是有本质的区别的,目标是完全解放繁琐的CRUD工作. 先总体介绍一下大概的架构和核心模块设计.先上一张 ...
- 企业级自定义表单引擎解决方案(十二)--体验代码目录结构
- 用户信息填写web代码_基于web的自定义表单引擎
所谓表单引擎 表单引擎,又可称之为表单流程.流程表单.工作流表单,是基于Web界面上可视化编辑的表单设计系统,可设置数据库的字段.属性,设置模块的配置.目前市面上的地带吗无代码表单引擎,可以为企业信息 ...
- 两种自定义表单设计方案 [转贴]
一直想搞定这个问题,最近在网络上搜索了一下查找些资料 最近参与一个项目,客户要求提供自定义表单的功能.主要的要求是:能够对表单的字段进行增删改,对显示表单的格式可以灵活定制.由于客户的表单变动可能比较 ...
- 转] 两种自定义表单设计方案
无涯 原文 两种自定义表单设计方案 [原创] 2006-12 最近参与一个项目,客户要求提供自定义表单的功能.主要的要求是:能够对表单的字段进行增删改,对显示表单的格式可以灵活定制.由于客户的表单变动 ...
- 老周谈低代码开发平台、工作流引擎、与表单引擎之间的关系
低代码开发是最近几年比较火的一个概念,而工作流引擎,表单引擎是好多年以前的产物了.今天我们就聊聊三者之间的关系. 我是开源驰骋BPM公司的老周,是bpm系统的爱好者.研发者.探索者. 对上述三个模块问 ...
- java中自定义表单和流程_让驰骋工作流程引擎 ccbpm使用自定义表单来实现自己的业务逻辑....
1.1.1.1: SDK表单 概要说明:我们把流程引擎与表单引擎统称为ccbpm,但是有一些用户并不想使用表单引擎,而是用自己的表单,仅仅使用流程引擎,这样的方式就要采用ccbpm的sdk表单开发模式 ...
- java 自定义表单 动态表单 表单设计器 工作流引擎 flowable
自定义表单设计模块都有哪些? 1 定义模版:拖拽左侧表单元素到右侧区域,编辑表单元素,保存表单模版 2 表单模版:编辑维护表单模版,复制表单模版,修改模版类型,预览表单模版 3. 我的表单:选择表单模 ...
- 国内无代码平台表单引擎功能对比
表单引擎是为快速实施项目研发的轻量级表单设计工具.采用表单引擎工具可在不开发和新增加代码的情况下设计出新表单样式,同比程序开发可省掉程序员差不多70%的开发工作量,并且后期维护相对简单,管理方便,管理 ...
最新文章
- 神奇的HTML5离线存储(应用程序缓存)
- 开放一些常见功能的工具类代码
- AC日记——字符串P型编码 openjudge 1.7 31
- caffe官网的部分翻译及NG的教程
- 重学python入门知识
- Android 系统(264)---android进阶——自定义View
- 通过set赋值,与select赋值的区别
- OpenStack云计算快速入门之三:OpenStack镜像管理
- 大数据 数据平台方案评估
- Java基础语法三 输入输出
- HDU Today--hdu2112
- 怎样设置电脑壁纸_怎样把C盘设置成禁止安装任何软件?教你两个方法,告别电脑卡顿...
- 水经注万能地图下载器如何导出透明TIF标签
- 用Peering Connections将多个VPC私网打通
- 建筑企业收并购系列二:吸收合并政策影响
- 【功能代码设计】3D坦克大战
- 微信公众平台生成二维码海报是如何做到的?
- CPU消耗高,tps低问题排查分析
- 使用Python转换PDF,Word/Excel/PPT/md/HTML都能转!
- 自由落体java编程_java模拟自由落体运动源代码
热门文章
- 核音智言 | 数字孪生IOC,助力城市治理智能高效!
- python列表or字符串切片步长为负数的情况分析
- 沙拉划词+福昕 文献翻译
- MATLAB程序:BPSK/QPSK的调制与解调
- echarts 矩阵图用法
- mysql查询1万条数据要1秒钟_SQL查询效率:100万数据查询只需要1秒钟
- pc android ios 学英语,全面升级更新.免费 小学英语人教版.三起[Android.iOS]
- 量化投资:为什么在中国发展之路任重而道远
- CentOS7 系统安全加固实施方案介绍
- 期指量仓均降 多空谨慎迎接收官日