Implementing Business Risk Control with Redis+Lua and Spring AOP Annotations in Kotlin
This article explains how to design and implement a business risk‑control component using Redis + Lua scripts and Spring AOP annotations in Kotlin, covering rule definitions, counting strategies, annotation handling, and testing procedures.
Background: The product uses many AI capabilities (OCR, speech evaluation) that are costly, so a business‑level risk control is needed to limit usage per user.
Why build custom risk control: Open‑source generic risk‑control components cannot satisfy the complex, multi‑dimensional rules required by the business, such as per‑user and per‑grade limits.
Requirements: Real‑time adjustable limits, support for natural‑day, natural‑hour, and combined day‑hour counting.
Implementation ideas: Choose between MySQL + transaction, Redis + Lua, or distributed transactions; the article selects Redis + Lua for simplicity and atomicity.
Rule implementation: Define three rules – natural‑day count, natural‑hour count, and combined day‑hour count. The combined rule must roll back the other counter when one condition fails.
Lua script for single‑dimensional counting:
local currentValue = redis.call('get', KEYS[1]); if currentValue ~= false then if tonumber(currentValue) < tonumber(ARGV[1]) then return redis.call('INCR', KEYS[1]); else return tonumber(currentValue) + 1; end; else redis.call('set', KEYS[1], 1, 'px', ARGV[2]); return 1; end;Lua script for day‑hour combined counting (includes rollback logic):
local dayValue = 0; local hourValue = 0; local dayPass = true; local hourPass = true; local dayCurrentValue = redis.call('get', KEYS[1]); if dayCurrentValue ~= false then if tonumber(dayCurrentValue) < tonumber(ARGV[1]) then dayValue = redis.call('INCR', KEYS[1]); else dayPass = false; dayValue = tonumber(dayCurrentValue) + 1; end; else redis.call('set', KEYS[1], 1, 'px', ARGV[3]); dayValue = 1; end; local hourCurrentValue = redis.call('get', KEYS[2]); if hourCurrentValue ~= false then if tonumber(hourCurrentValue) < tonumber(ARGV[2]) then hourValue = redis.call('INCR', KEYS[2]); else hourPass = false; hourValue = tonumber(hourCurrentValue) + 1; end; else redis.call('set', KEYS[2], 1, 'px', ARGV[4]); hourValue = 1; end; if (not dayPass) and hourPass then hourValue = redis.call('DECR', KEYS[2]); end; if dayPass and (not hourPass) then dayValue = redis.call('DECR', KEYS[1]); end; local pair = {}; pair[1] = dayValue; pair[2] = hourValue; return pair;Annotation approach: Define a @Detect annotation with eventId and contentSpel attributes, and implement a DetectHandler aspect that parses the SpEL expression, obtains the content, and invokes DetectManager to enforce the rule.
@Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) annotation class Detect(@...) @Aspect @Component class DetectHandler { ... }Usage example: Annotate the OCR service method with @Detect(eventId = "ocr", contentSpel = "#arg1") so that each call checks the user‑id against the Redis counter.
@Service class OcrServiceImpl : OcrService { @Detect(eventId = "ocr", contentSpel = "#arg1") override fun submitOcrTask(userId: String, imageUrl: String): String { /* do ocr */ } }Testing confirms that the annotation values are retrieved and the SpEL expression is evaluated correctly.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
