计划任务编辑详情前后端适配
This commit is contained in:
164
src/main/java/quant/rich/emoney/util/ArithmeticCaptchaGen.java
Normal file
164
src/main/java/quant/rich/emoney/util/ArithmeticCaptchaGen.java
Normal file
@@ -0,0 +1,164 @@
|
||||
package quant.rich.emoney.util;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class ArithmeticCaptchaGen {
|
||||
|
||||
private static final SecureRandom RND = new SecureRandom();
|
||||
private static final char[] OPS = new char[]{'+', '-', '×', '÷'};
|
||||
|
||||
public static class Captcha {
|
||||
public final int a;
|
||||
public final int b;
|
||||
public final char op;
|
||||
|
||||
public Captcha(int a, int b, char op) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public String expr() {
|
||||
return a + " " + op + " " + b;
|
||||
}
|
||||
|
||||
public int answer() {
|
||||
return switch (op) {
|
||||
case '+' -> a + b;
|
||||
case '-' -> a - b;
|
||||
case '×' -> a * b;
|
||||
case '÷' -> a / b; // 保证整除
|
||||
default -> throw new IllegalStateException("Unexpected op: " + op);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return expr() + " = ?";
|
||||
}
|
||||
}
|
||||
|
||||
/** 对外:随机生成一题 */
|
||||
public static Captcha generate() {
|
||||
char op = OPS[RND.nextInt(OPS.length)];
|
||||
return generate(op);
|
||||
}
|
||||
|
||||
/** 对外:指定运算符生成 */
|
||||
public static Captcha generate(char op) {
|
||||
return switch (op) {
|
||||
case '+' -> genAddCarryAllowedButIfCarryUnitsZero();
|
||||
case '-' -> genSubNoBorrowNonNegative();
|
||||
case '×' -> genMulNoCarryOneDigit();
|
||||
case '÷' -> genDivExact();
|
||||
default -> throw new IllegalArgumentException("Unsupported op: " + op);
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------- +:允许进位;若发生进位,则结果个位必须为 0;且和 <= 100 ----------------
|
||||
private static Captcha genAddCarryAllowedButIfCarryUnitsZero() {
|
||||
// 仍保留 100 的特殊题(不是必须,但能保证覆盖 100)
|
||||
if (RND.nextInt(20) == 0) { // 5% 概率出 100
|
||||
if (RND.nextBoolean()) return new Captcha(100, 0, '+');
|
||||
return new Captcha(0, 100, '+');
|
||||
}
|
||||
|
||||
while (true) {
|
||||
int a = RND.nextInt(101); // 0..100
|
||||
int b = RND.nextInt(101); // 0..100
|
||||
int sum = a + b;
|
||||
if (sum > 100) continue;
|
||||
|
||||
if (carryHappened(a, b)) {
|
||||
// 发生进位:个位必须为 0
|
||||
if (sum % 10 == 0) return new Captcha(a, b, '+');
|
||||
} else {
|
||||
// 未发生进位:不限制个位
|
||||
return new Captcha(a, b, '+');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 判断十进制逐位相加是否发生过进位(任意一位) */
|
||||
private static boolean carryHappened(int a, int b) {
|
||||
int carry = 0;
|
||||
while (a > 0 || b > 0) {
|
||||
int da = a % 10;
|
||||
int db = b % 10;
|
||||
int s = da + db + carry;
|
||||
if (s >= 10) return true;
|
||||
carry = 0; // 因为 s<10 时 carry 必为 0
|
||||
a /= 10;
|
||||
b /= 10;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---------------- -:无退位,差 >= 0 ----------------
|
||||
private static Captcha genSubNoBorrowNonNegative() {
|
||||
while (true) {
|
||||
int a = RND.nextInt(100); // 0..99
|
||||
int b = RND.nextInt(100); // 0..99
|
||||
|
||||
int aT = a / 10, aU = a % 10;
|
||||
int bT = b / 10, bU = b % 10;
|
||||
|
||||
// 无退位:每一位都要 a>=b;且整体差>=0
|
||||
if (aU >= bU && aT >= bT) {
|
||||
return new Captcha(a, b, '-'); // 此时 a>=b 自然成立
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- *:无进位;一元为 0..9;另一元每位*d < 10 ----------------
|
||||
private static Captcha genMulNoCarryOneDigit() {
|
||||
while (true) {
|
||||
int d = RND.nextInt(10); // 0..9 其中一个因子
|
||||
int x = RND.nextInt(100); // 另一元 0..99(你可自行放大范围)
|
||||
|
||||
// 如果 d=0,任何 x 都满足
|
||||
if (d == 0) return orderMulOperands(d, x);
|
||||
|
||||
// 对 x 的每一位要求:digit*d < 10 才不会在该位产生进位
|
||||
if (allDigitsMulLessThan10(x, d)) {
|
||||
return orderMulOperands(d, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Captcha orderMulOperands(int d, int x) {
|
||||
// 随机决定把 0..9 放左边还是右边
|
||||
if (RND.nextBoolean()) return new Captcha(d, x, '×');
|
||||
return new Captcha(x, d, '×');
|
||||
}
|
||||
|
||||
private static boolean allDigitsMulLessThan10(int x, int d) {
|
||||
// x 是非负
|
||||
if (x == 0) return true;
|
||||
int t = x;
|
||||
while (t > 0) {
|
||||
int digit = t % 10;
|
||||
if (digit * d >= 10) return false;
|
||||
t /= 10;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---------------- /:必须整除 ----------------
|
||||
private static Captcha genDivExact() {
|
||||
// 控制题目大小: dividend <= 100
|
||||
while (true) {
|
||||
int b = 1 + RND.nextInt(10); // 除数 1..10
|
||||
int q = RND.nextInt(21); // 商 0..20
|
||||
int a = b * q; // 被除数
|
||||
if (a <= 100) return new Captcha(a, b, '÷');
|
||||
}
|
||||
}
|
||||
|
||||
// demo
|
||||
public static void main(String[] args) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
Captcha c = generate();
|
||||
System.out.println(c.expr() + " = " + c.answer());
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/main/java/quant/rich/emoney/util/ChunkRandomIter.java
Normal file
28
src/main/java/quant/rich/emoney/util/ChunkRandomIter.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package quant.rich.emoney.util;
|
||||
import java.util.*;
|
||||
|
||||
public class ChunkRandomIter {
|
||||
|
||||
final static Random RANDOM = new Random();
|
||||
|
||||
public static <T> List<T> splitShuffleAndFlatten(List<T> list, int n) {
|
||||
int size = list.size();
|
||||
if (n <= 0) throw new IllegalArgumentException("n must be > 0");
|
||||
n = Math.min(n, size == 0 ? 1 : size);
|
||||
|
||||
List<T> parts = new ArrayList<>(n);
|
||||
|
||||
int base = size / n; // 每份至少 base 个
|
||||
int extra = size % n; // 前 extra 份多一个
|
||||
|
||||
int idx = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
int partSize = base + (i < extra ? 1 : 0);
|
||||
List<T> part = new ArrayList<>(list.subList(idx, idx + partSize));
|
||||
Collections.shuffle(part, RANDOM);
|
||||
parts.addAll(part);
|
||||
idx += partSize;
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package quant.rich.emoney.util;
|
||||
|
||||
import java.time.DateTimeException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
@@ -55,4 +56,40 @@ public final class DateUtils {
|
||||
LocalDate date = LocalDate.parse(value, formatter);
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数字类型日期转换成相应的 LocalDateTime
|
||||
* <p>
|
||||
* <li> 2309191130 → 2023-09-19 11:30:00
|
||||
* <li> 230919 → 2023-09-19
|
||||
*/
|
||||
public static LocalDateTime longDatetimeToLocalDateTime(long dateTime) {
|
||||
if (dateTime <= 0) {
|
||||
throw new IllegalArgumentException("dateTime must be positive, got: " + dateTime);
|
||||
}
|
||||
|
||||
try {
|
||||
if (dateTime > 99_999_999L) {
|
||||
// yyMMddHHmm 例如:2309191130 -> 2023-09-19 11:30
|
||||
int yy = (int) (dateTime / 100_000_000L); // 23
|
||||
int year = 2000 + yy; // 2023
|
||||
int month = (int) ((dateTime % 100_000_000L) / 1_000_000L); // 09
|
||||
int day = (int) ((dateTime % 1_000_000L) / 10_000L); // 19
|
||||
int hour = (int) ((dateTime % 10_000L) / 100L); // 11
|
||||
int minute = (int) (dateTime % 100L); // 30
|
||||
|
||||
return LocalDateTime.of(year, month, day, hour, minute, 0);
|
||||
} else {
|
||||
// yyyyMMdd 例如:20230919 -> 2023-09-19 00:00
|
||||
int year = (int) (dateTime / 10_000L); // 2023
|
||||
int month = (int) ((dateTime % 10_000L) / 100L); // 09
|
||||
int day = (int) (dateTime % 100L); // 19
|
||||
|
||||
return LocalDateTime.of(year, month, day, 0, 0, 0);
|
||||
}
|
||||
} catch (DateTimeException ex) {
|
||||
// 月/日/时/分越界会进这里(比如 month=13)
|
||||
throw new IllegalArgumentException("Invalid dateTime value: " + dateTime, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user