枚举 + Option 的动态鉴权
使用场景:当有一组规则需要进行配置及匹配,通过枚举完成封装,但是在进行判断的时候通过 Option 来干掉 if-else
枚举类
public enum RoleEnum {
ADMIN("admin", "管理员", ""),
NORMAL("normal", "普通用户", "dataZt"),
LEADER("leader", "领导", "userList")
;
private String code;
private String desc;
private String homeUrl;
private static final Map<String, RoleEnum> CODE_MAP = new HashMap<>();
static {
for (RoleEnum role : values()) {
CODE_MAP.put(role.code.toLowerCase(), role);
}
}
RoleEnum(String code, String desc, String homeUrl) {
this.code = code;
this.desc = desc;
this.homeUrl = homeUrl;
}
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
public String getHomeUrl() {
return homeUrl;
}
public static Optional<RoleEnum> fromCode(String code) {
return Optional.ofNullable(CODE_MAP.get(code.toLowerCase()));
}
}
调用者
RoleEnum enmu = RoleEnum.fromCode("admin")
.orElseThrow(() -> new BizException("该类型不存在"));
注解实现 map > 实体的映射转换
注解类
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ElementK {
String value();
boolean required() default true;
}
属性转换工具类
import cn.hutool.core.util.StrUtil;
import com.haigui.pangu.exception.BizException;
import com.haigui.qingting.anno.ElementK;
import java.lang.reflect.Field;
import java.util.Map;
public class ConvertUtil {
/**
* 自动转换属性
*
* @param elements 元素表
* @param clazz
* @param <T>
* @return
*/
public static <T> T convertN(Map<String, String> elements, Class<T> clazz) {
T instance = null;
try {
instance = clazz.newInstance();
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
if (f.isAnnotationPresent(ElementK.class)) {
ElementK anno = f.getAnnotation(ElementK.class);
String key = anno.value();
boolean required = anno.required();
if (required && StrUtil.isBlank(elements.get(key))) {
throw new BizException("要素【" + key + "】缺失");
}
if (elements.containsKey(key)) {
f.setAccessible(true);
f.set(instance, elements.get(key));
}
}
}
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
return instance;
}
}
调用者
// 以业务中的json串为例
public static <T> T checkElement(String json, Class<T> clazz) {
Map<String, String> elements = JSONUtil.toBean(JSONUtil.parseObj(json).getJSONObject("elements"), Map.class);
return ConvertUtil.convertN(elements, clazz);
}
Element element = CommonScriptUtil.checkElement(json, Element.class);
实体
public class Element {
@ElementK("民初案号")
private String minChuAnHao;
@ElementK("被告名称")
private String beiGao;
@ElementK("案由")
private String anYou;
@ElementK("执行标的")
private String zhiXingBiaoDi;
// set get toString...
}
autoIT实现文件上传的封装
此处特色,根据文件地址,标题,驱动,以及consumer,可以自定义上传的一些动作
/**
*
* @param filePath 文件路径
* @param title 上传标题,用于日志输出
* @param driver driver
* @param driverAction action
*/
public static void uploadByAutoIT(String filePath, String title, ChromeWebDriver driver, Consumer<ChromeWebDriver> driverAction) {
if (x == null) {
x = AutoITUtil.getInstance().getAutoItX();
}
log.info("开始上传<{}>附件", title);
// 当前时间
long tempStartTime = System.currentTimeMillis();
// 等待时间
while (!x.winWait("打开", "", 3)) {
if (System.currentTimeMillis() - tempStartTime > 90000) {
throw new BizException("文件上传异常");
}
driverAction.accept(driver);
}
tempStartTime = System.currentTimeMillis();
//上传文件
while (x.winWait("打开", "", 3)) {
if (System.currentTimeMillis() - tempStartTime > 90000) {
throw new BizException("文件上传异常");
}
x.winActivate("打开", "");
// 输入框
x.ControlSetText("打开", "", "Edit1", filePath);
// 点击按钮
x.controlClick("打开", "", "Button1");
}
log.info("选择<{}: {}>附件完成", title, filePath);
}
调用者
CommonScriptUtil.uploadByAutoIT(FILE_MAP.get("申请书").getAbsolutePath(), "申请书", driver, driver -> driver.actionClick(By.xpath("//div[text()='添加文件']/../div")));
文件下载并移动及重命名
/**
* 下载并移动文件
* @param keyword 关键字
* @param operateFile 操作流
*/
public static void downLoadFileAndMove(String keyword, Function<File, String> operateFile) {
int times = 1;
List<File> downloadFileList;
while (true) {
if (times == 30) {
throw new BizException("下载失败, 请检查是否被浏览器拦截或者网速太慢");
}
ThreadUtil.sleep(2000L);
downloadFileList = FileUtil.loopFiles(DOWNLOAD_DIR, f -> f.getAbsolutePath().contains(keyword));
if (downloadFileList.isEmpty()) {
times++;
} else if (downloadFileList.size() > 1) {
throw new BizException("下载的文件疑似存在多个, 请手动确认 >>> " + downloadFileList);
} else {
File file = downloadFileList.get(0);
FileUtil.move(file, new File(operateFile.apply(file)), true);
ThreadUtil.sleep(2000L);
break;
}
}
}
调用
CommonScriptUtil.downLoadFileAndMove("交纳诉讼费通知书-渤海银行股份有限公司", f -> DOWNLOAD_PATH + "交纳诉讼费通知书-" + anHao +".pdf");
基于selelium的handle切换
/**
* 切换新窗口
* @param driver
* @param xpath
*/
public static void switchNewWindow(ChromeWebDriver driver, String xpath) {
// 点击-案号链接
driver.click(By.xpath(xpath));
driver.sleep(2000L);
ArrayList<String> newHandles = new ArrayList<>(driver.getWindowHandles());
log.warn("dee >>> 即将切换新窗口 = {}", driver.getWindowHandle());
driver.switchWindow(newHandles.get(newHandles.size() - 1));
}
public static void toPageByTitle(ChromeWebDriver driver, String title) {
Set<String> windowHandles = driver.getWebDriver().getWindowHandles();
log.info("当前所有的handles: 【{}】, 个数{}", JSONUtil.toJsonStr(windowHandles), windowHandles.size());
for (String handle : windowHandles) {
if (driver.getWebDriver().switchTo().window(handle).getTitle().contains(title)) {
log.warn("dee >>> 当前窗口标题为: 【{}】", driver.getWebDriver().switchTo().window(handle).getTitle());
driver.getWebDriver().switchTo().window(handle);
break;
}
}
}
public static void closeAndToPageByTitle(ChromeWebDriver driver, String title) {
driver.sleep(2000L);
if (driver.getWindowHandles().size() == 1) {
log.warn("dee >>> 仅剩一个窗口, 阻止关闭并转到首页【天津办公办案平台】(此处逻辑可根据现场具体调整)");
toPageByTitle(driver, PageEnum.天津办公办案平台.name());
driver.sleep(1000L);
return;
// 如果当前页已经在首页, 则不动
} else if (driver.getTitle().contains(PageEnum.天津办公办案平台.name())) {
// 等2s
driver.sleep(2000L);
toPageByTitle(driver, title);
}
// 关闭当前页
log.warn("dee >>> 已关闭当前页");
driver.close();
// 等2s
driver.sleep(2000L);
toPageByTitle(driver, title);
}
debug模式启动chrome, 并通过selenium进行连接
浏览器快捷方式属性配置
"C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222 --user-data-dir="C:\Users\haigui\AppData\Local\Google\Chrome\User Data"
import com.haigui.tourmaline.selenium.driver.ChromeWebDriver;
import com.haigui.tourmaline.selenium.util.ThreadDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import org.openqa.selenium.chrome.ChromeOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
public class GridUtil {
private static final Logger log = LoggerFactory.getLogger(GridUtil.class);
public static ChromeWebDriver driver;
public static ChromeWebDriver getChromeDriver(String driverPath, String binary) {
ThreadDriver.CurrentThreadDriver threadDriver = ThreadDriver.get();
driver = threadDriver.getChromeWebDriver();
if (driver != null) {
return driver;
}
ChromeOptions options = new ChromeOptions();
options.setBinary(new File(binary));
// 不检查是否为默认浏览器
options.addArguments("--no-default-browser-check");
options.addArguments("--no-first-run");
// "C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222 --user-data-dir="C:\Users\haigui\AppData\Local\Google\Chrome\User Data"
options.setExperimentalOption("debuggerAddress", "127.0.0.1:9222"); // 与浏览器配置保持一致
// chrome打开设置
ChromeDriverService service = new ChromeDriverService
.Builder()
.usingDriverExecutable(new File(driverPath))
.build();
driver = new ChromeWebDriver(new ChromeDriver(service, options));
threadDriver.setAbstractWebDriver(driver);
threadDriver.setChromeWebDriver(driver);
ThreadDriver.set(threadDriver);
return driver;
}
}
记一次selenium的js操作
前置说明:网页端有一个按钮点击报错,经排查最终确定原因:网页中将 Lodash 注入并引用赋值给
_
,但是点击按钮前有一个 input 操作,input 之后会替换_
。因此解决思路是尽量避免替换_
, 或者在 input 前后使用 js 操作改变方法
方案一:避免使用 selenium 的 input 操作
driver.copy("地址...");
driver.click(By.xpath("//input[@id='kw']")); // 点击输入框
driver.paste(); // 这里不确定能不能粘贴成功
原生 selenium
// 定位输入框
WebElement input = driver.findElement(By.cssSelector(inputSelector));
// 点击输入框(聚焦)
input.click();
// 将文本复制到剪贴板(使用 AWT 工具类)
StringSelection stringSelection = new StringSelection(text);
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(stringSelection, null);
// 模拟粘贴操作(Mac 用 Keys.COMMAND,Windows 用 Keys.CONTROL)
Actions actions = new Actions(driver);
actions.keyDown(Keys.CONTROL)
.sendKeys("v")
.keyUp(Keys.CONTROL)
.perform();
// 验证输入内容(可选)
String value = input.getAttribute("value");
if (!value.equals(text)) {
throw new RuntimeException("粘贴失败,输入框内容不符");
}
方案二:在输入前备份变量
String backupScript =
"window.__originalLodash = window._;"; // 将 _ 存储到 __originalLodash
((JavascriptExecutor) driver.getWebDriver()).executeScript(backupScript);
// 输入的逻辑。。。
String restoreScript =
"window._ = window.__originalLodash;";
((JavascriptExecutor) driver.getWebDriver()).executeScript(restoreScript);
// 验证 _.cloneDeep 是否恢复
Boolean isLodashValid = (Boolean) ((JavascriptExecutor) driver.getWebDriver())
.executeScript("return typeof _.cloneDeep === 'function'");
System.out.println("Lodash 是否有效: " + isLodashValid); // 应为 true
方案三:替换_.cloneDeep
方法
String patchCloneDeep =
"if (typeof _.cloneDeep === 'undefined') { " +
" _.cloneDeep = function(obj) { " +
" return JSON.parse(JSON.stringify(obj)); " +
" };" +
"}";
((JavascriptExecutor) driver.getWebDriver()).executeScript(patchCloneDeep);
方案四:绕过输入事件触发
WebElement input = driver.findElement(By.xpath("输入框选择器"));
// 通过 JavaScript 直接设置值(不触发 input 事件)
String script = "arguments[0].value = '" + "输入内容" + "';";
((JavascriptExecutor) driver.getWebDriver()).executeScript(script, input);
// 验证输入框值是否设置成功
System.out.println("输入框值: " + input.getAttribute("value")); // 应输出 "输入内容"