当前位置: 首页 > news >正文

java 对mybatis拦截Interceptor进行权限控制(条件修改),入参修改,返回修改

使用jsqlparser对sql解析拼装

基础准备

<dependency><groupId>com.github.jsqlparser</groupId><artifactId>jsqlparser</artifactId><version>4.5</version><scope>compile</scope>
</dependency>
<dependency><groupId>commons-beanutils</groupId><artifactId>commons-beanutils</artifactId><version>1.9.4</version>
</dependency>
import lombok.Data;
/*** 需要拦截或修改的值*/
@Data
public class InitiateInterceptField {public static final String deptId = "deptId";public static final String createBy = "createBy";public static final String updateBy = "updateBy";public static final String dept_id = "dept_id";public static final String create_by = "create_by";public static final String update_by = "update_by";public static final String deptName = "deptName";public static final String createName = "createName";public static final String updateName = "updateName";}
import cn.hutool.core.thread.ThreadFactoryBuilder;
import lombok.Data;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;import java.util.concurrent.*;@Data
@Component
public class DisclosureThreadPool {@Bean("mybatisInterceptorExecutorService")public ExecutorService entranceAlerterThread() {ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNamePrefix("thread-mybatisInterceptorExecutorService-runner-%d").build();return new ThreadPoolExecutor(4, 36, 200L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(204800), namedThreadFactory);}}
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;import java.lang.reflect.Proxy;
import java.util.Properties;/*** 插件工具类*/
public final class PluginUtils {public static final String DELEGATE_BOUNDSQL_SQL = "delegate.boundSql.sql";private PluginUtils() {// to do nothing}/*** 获得真正的处理对象,可能多层代理.*/@SuppressWarnings("unchecked")public static <T> T realTarget(Object target) {if (Proxy.isProxyClass(target.getClass())) {MetaObject metaObject = SystemMetaObject.forObject(target);return realTarget(metaObject.getValue("h.target"));}return (T) target;}/*** 根据 key 获取 Properties 的值*/public static String getProperty(Properties properties, String key) {String value = properties.getProperty(key);return StringUtils.isBlank(value) ? null : value;}
}

数据权限控制

比如根据部门权限动态修改sql

import cn.hutool.core.util.ObjectUtil;
import com.disclosure.common.core.domain.entity.SysRole;
import com.disclosure.common.core.domain.entity.SysUser;
import com.disclosure.common.core.domain.model.LoginUser;
import com.disclosure.common.utils.SecurityUtils;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;import java.sql.Connection;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ExecutorService;/*** @author <p>* mybatis 数据权限拦截器*/
@Slf4j
@AllArgsConstructor
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare" , args = {Connection.class, Integer.class})})
public class AuthorityScopeInterceptor implements Interceptor {private final ExecutorService executorService;@Override@SneakyThrowspublic Object intercept(Invocation invocation) {StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());MetaObject metaObject = SystemMetaObject.forObject(statementHandler);MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");//指定范围拦截if (!mappedStatement.getId().startsWith("com.disclosure.business")) return invocation.proceed();//只拦截 三种类型if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())&& !SqlCommandType.UPDATE.equals(mappedStatement.getSqlCommandType())&& !SqlCommandType.DELETE.equals(mappedStatement.getSqlCommandType())) return invocation.proceed();//获取用户属性LoginUser loginUser = SecurityUtils.getLoginUser();SysUser currentUser;if (ObjectUtil.isNull(loginUser) || ObjectUtil.isNull(currentUser = loginUser.getUser()))throw new RuntimeException("获取不到用户数据");// 如果是超级管理员,则不过滤数据if (currentUser.isAdmin()) return invocation.proceed();List<SysRole> roles = currentUser.getRoles();if (roles.isEmpty()) throw new RuntimeException("角色缺失");AuthorityCategory authorityCategory = new AuthorityCategory(executorService);//获取多角色的权限范围for (SysRole role : roles) {switch (role.getDataScope()) {case AuthorityCategory.DATA_SCOPE_ALL:return invocation.proceed();case AuthorityCategory.DATA_SCOPE_CUSTOM:authorityCategory.addDataScopeCustom(role.getRoleId());break;case AuthorityCategory.DATA_SCOPE_DEPT:authorityCategory.addDataScopeDept(currentUser.getDeptId());break;case AuthorityCategory.DATA_SCOPE_DEPT_AND_CHILD:authorityCategory.addDataScopeDeptAndChild(currentUser.getDeptId());break;default:authorityCategory.addDataScopeSelf(currentUser.getUserId());break;}}authorityCategory.removeRepeatDept();BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");String originalSql = boundSql.getSql();//生成新sqloriginalSql = authorityCategory.getNewSql(originalSql).toString();metaObject.setValue("delegate.boundSql.sql" , originalSql);return invocation.proceed();}/*** 生成拦截对象的代理** @param target 目标对象* @return 代理对象*/@Overridepublic Object plugin(Object target) {if (target instanceof StatementHandler) {return Plugin.wrap(target, this);}return target;}/*** mybatis配置的属性** @param properties mybatis配置的属性*/@Overridepublic void setProperties(Properties properties) {}
}
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.Data;
import lombok.SneakyThrows;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import net.sf.jsqlparser.statement.update.Update;import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.Stream;import static com.disclosure.business.config.InitiateInterceptField.create_by;
import static com.disclosure.business.config.InitiateInterceptField.dept_id;@Data
public class AuthorityCategory {private final ExecutorService executorService;/*** 全部*/public static final String DATA_SCOPE_ALL = "1";/*** 自定数据权限*/public static final String DATA_SCOPE_CUSTOM = "2";/*** 部门数据权限*/public static final String DATA_SCOPE_DEPT = "3";/*** 部门及以下数据权限*/public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";/*** 仅本人数据权限*/public static final String DATA_SCOPE_SELF = "5";private ArrayList<Long> dataScopeCustomList;private ArrayList<Long> dataScopeDeptList;private ArrayList<Long> dataScopeDeptAndChildList;private ArrayList<Long> dataScopeSelfList;private String fromItemName;public AuthorityCategory(ExecutorService executorService) {this.executorService = executorService;this.dataScopeCustomList = new ArrayList<>();this.dataScopeDeptList = new ArrayList<>();this.dataScopeDeptAndChildList = new ArrayList<>();this.dataScopeSelfList = new ArrayList<>();this.fromItemName = "";}public void addDataScopeSelf(Long dataScopeSelf) {this.dataScopeSelfList.add(dataScopeSelf);}public void addDataScopeDeptAndChild(Long dataScopeDeptAndChild) {this.dataScopeDeptAndChildList.add(dataScopeDeptAndChild);}public void addDataScopeDept(Long dataScopeDept) {this.dataScopeDeptList.add(dataScopeDept);}public void addDataScopeCustom(Long dataScopeCustom) {this.dataScopeCustomList.add(dataScopeCustom);}public void removeRepeatDept() {dataScopeDeptList.removeAll(dataScopeDeptAndChildList);}@SneakyThrowspublic Statement getNewSql(String originalSql) {//解析只针对主表Statement statement = CCJSqlParserUtil.parse(new StringReader(originalSql));if (statement instanceof Select) {Select select = (Select) statement;SelectBody selectBody = select.getSelectBody();PlainSelect plainSelect = (PlainSelect) selectBody;plainSelect.setWhere(spliceSQl(plainSelect.getWhere(), plainSelect.getFromItem()));return select;} else if (statement instanceof Delete) {Delete delete = (Delete) statement;delete.setWhere(spliceSQl(delete.getWhere(), delete.getTable()));return delete;} else if (statement instanceof Update) {Update update = (Update) statement;update.setWhere(spliceSQl(update.getWhere(), update.getTable()));return update;}return statement;}private Expression spliceSQl(Expression expression, FromItem fromItem) {List<Expression> expressionList = this.newWhere(fromItem);if (ObjectUtil.isNull(expression)) {return getOr(expressionList);} else {AndExpression andExpression = new AndExpression();andExpression.setLeftExpression(expression);andExpression.setRightExpression(new Column("(" + getOr(expressionList) + ")"));return expression;}}/*** 处理 四种权限类型*/@SneakyThrowsprivate List<Expression> newWhere(FromItem fromItem) {handleFromItemName(fromItem);Future<Expression> handleDataScopeSelfLisSubmit = executorService.submit(this::handleDataScopeSelfList);Future<Expression> handleDataScopeDeptListSubmit = executorService.submit(this::handleDataScopeDeptList);Future<Expression> handleDataScopeCustomListSubmit = executorService.submit(this::handleDataScopeCustomList);Future<Expression> handleDataScopeDeptAndChildListSubmit = executorService.submit(this::handleDataScopeDeptAndChildList);return Stream.of(handleDataScopeSelfLisSubmit.get(),handleDataScopeDeptListSubmit.get(),handleDataScopeCustomListSubmit.get(),handleDataScopeDeptAndChildListSubmit.get()).filter(ObjectUtil::isNotNull).collect(Collectors.toList());}/*** 获取名字或别名*/private void handleFromItemName(FromItem fromItem) {if (ObjectUtil.isNull(fromItem.getAlias())) {this.fromItemName = fromItem.toString();} else {this.fromItemName = fromItem.getAlias().getName();}}/*** 拼接仅本人数据权限sql*/private Expression handleDataScopeSelfList() {if (!this.dataScopeSelfList.isEmpty()) {InExpression inExpression = new InExpression();inExpression.setLeftExpression(new Column(new Table(this.fromItemName), create_by));inExpression.setRightExpression(new Column("(" + CollUtil.join(this.dataScopeSelfList, ",") + ")"));return new Column("(" + inExpression + ")");}return null;}/*** 拼接仅部门数据权限sql*/private Expression handleDataScopeDeptList() {if (!this.dataScopeDeptList.isEmpty()) {InExpression inExpression = new InExpression();inExpression.setLeftExpression(new Column(new Table(this.fromItemName), dept_id));inExpression.setRightExpression(new Column("(" + CollUtil.join(this.dataScopeDeptList, ",") + ")"));return new Column("(" + inExpression + ")");}return null;}/*** 拼接自定义数据权限sql*/private Expression handleDataScopeCustomList() {if (!this.dataScopeCustomList.isEmpty()) {InExpression inExpression = new InExpression();inExpression.setLeftExpression(new Column(new Table(this.fromItemName), dept_id));inExpression.setRightExpression(new Column("(SELECT dept_id FROM sys_role_dept WHERE role_id in (" + CollUtil.join(this.dataScopeCustomList, ",") + "))"));return new Column("(" + inExpression + ")");}return null;}/*** 拼接部门及以下数据权限sql*/private Expression handleDataScopeDeptAndChildList() {if (!this.dataScopeDeptAndChildList.isEmpty()) {InExpression inExpression = new InExpression();inExpression.setLeftExpression(new Column(new Table(this.fromItemName), dept_id));inExpression.setRightExpression(new Column("(SELECT dept_id FROM sys_dept WHERE dept_id in (" + CollUtil.join(this.dataScopeDeptAndChildList, ",") + ") or " + this.dataScopeDeptAndChildList.parallelStream().map(n -> "find_in_set(" + n + ",ancestors)").collect(Collectors.joining(" or ")) + ")"));return new Column("(" + inExpression + ")");}return null;}private Expression getOr(List<Expression> expressions) {if (expressions.size() == 1) return expressions.get(0);String collect = expressions.parallelStream().map(Object::toString).collect(Collectors.joining(" or "));return new Column(collect);}}

入参修改

比如修改机构字段,删除机构字段,填充新建用户

import cn.hutool.core.util.ObjectUtil;
import com.disclosure.common.core.domain.entity.SysUser;
import com.disclosure.common.core.domain.model.LoginUser;
import com.disclosure.common.utils.SecurityUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.update.UpdateSet;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;import java.io.StringReader;
import java.sql.Connection;
import java.util.*;
import java.util.stream.Collectors;import static com.disclosure.business.config.InitiateInterceptField.*;@Slf4j
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare" , args = {Connection.class, Integer.class})})
public class AuthorityUserFillerInterceptor implements Interceptor {//清理指定映射字段private final String[] deleteParameter = {deptId, createBy, updateBy};private final String[] deleteColumn = {dept_id, create_by, update_by};@Override@SneakyThrowspublic Object intercept(Invocation invocation) {StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());MetaObject metaObject = SystemMetaObject.forObject(statementHandler);MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");if (!mappedStatement.getId().startsWith("com.disclosure.business")) return invocation.proceed();if (!SqlCommandType.UPDATE.equals(mappedStatement.getSqlCommandType())&& !SqlCommandType.INSERT.equals(mappedStatement.getSqlCommandType())) {return invocation.proceed();}LoginUser loginUser = SecurityUtils.getLoginUser();SysUser currentUser;if (ObjectUtil.isNull(loginUser) || ObjectUtil.isNull(currentUser = loginUser.getUser()))throw new RuntimeException("获取不到用户数据");BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");String originalSql = boundSql.getSql();Statement statement = CCJSqlParserUtil.parse(new StringReader(originalSql));List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();//删除参数映射以免出现下列问题//        Parameter index out of range (X > number of parameters, which is X)List<String> deleteParameterList = Arrays.asList(deleteParameter);List<ParameterMapping> deptIdParameterMappings = parameterMappings.stream().parallel().map(n -> {if (deleteParameterList.contains(n.getProperty())) {return n;}return null;}).filter(ObjectUtil::isNotNull).collect(Collectors.toList());if (!deptIdParameterMappings.isEmpty()) {parameterMappings.removeAll(deptIdParameterMappings);}if (statement instanceof Update) {Update update = (Update) CCJSqlParserUtil.parse(originalSql);ArrayList<UpdateSet> updateSets = update.getUpdateSets();//删除sql对应值updateCleanse(updateSets);//填充信息Map<String, Object> updateParameter = new HashMap<String, Object>() {{put(update_by, currentUser.getUserId());}};setUpdateParameter(updateSets, updateParameter);originalSql = update.toString();} else if (statement instanceof Insert) {Insert insert = (Insert) CCJSqlParserUtil.parse(originalSql);List<Column> columns = insert.getColumns();List<Expression> expressions = insert.getItemsList(ExpressionList.class).getExpressions();insertCleanse(columns, expressions);Map<String, Object> insertParameter = new HashMap<String, Object>() {{put(dept_id, currentUser.getDeptId());put(create_by, currentUser.getUserId());}};//填充信息setInsertParameter(columns, expressions, insertParameter);originalSql = insert.toString();} else {return invocation.proceed();}metaObject.setValue("delegate.boundSql.sql" , originalSql);return invocation.proceed();}/*** 填充Insert入参,只做string和long,可另行扩展*/private void setInsertParameter(List<Column> columns, List<Expression> expressions, Map<String, Object> insertParameter) {insertParameter.forEach((k, v) -> {columns.add(new Column(k));if (v instanceof Long) {expressions.add(new LongValue((long) v));} else {expressions.add(new StringValue((String) v));}});}/*** 填充update入参,只做string和long,可另行扩展*/public void setUpdateParameter(List<UpdateSet> updateSets, Map<String, Object> updateParameter) {updateParameter.forEach((k, v) -> {UpdateSet updateSet = new UpdateSet();if (v instanceof Long) {updateSet.add(new Column(k), new LongValue((long) v));} else {updateSet.add(new Column(k), new StringValue(String.valueOf(v)));}updateSets.add(updateSet);});}/*** 删除update数据*/private void updateCleanse(ArrayList<UpdateSet> updateSets) {List<String> deleteColumnList = Arrays.asList(deleteColumn);List<UpdateSet> deleteUpdateSets = updateSets.parallelStream().map(n -> {ArrayList<Column> columns = n.getColumns();for (Column column : columns) {if (deleteColumnList.contains(column.toString())) {return n;}}return null;}).filter(ObjectUtil::isNotNull).collect(Collectors.toList());if (!deleteUpdateSets.isEmpty()) {updateSets.removeAll(deleteUpdateSets);}}/*** 删除insert数据*/private void insertCleanse(List<Column> columns, List<Expression> expressions) {List<String> deleteColumnList = Arrays.asList(deleteColumn);//删除sql对应值List<Column> deleteColumn = columns.parallelStream().map(n -> {if (deleteColumnList.contains(n.toString())) {return n;}return null;}).filter(ObjectUtil::isNotNull).collect(Collectors.toList());if (!deleteColumn.isEmpty()) {deleteColumn.parallelStream().forEach(n -> expressions.remove(columns.indexOf(n)));columns.removeAll(deleteColumn);}}/*** 生成拦截对象的代理** @param target 目标对象* @return 代理对象*/@Overridepublic Object plugin(Object target) {if (target instanceof StatementHandler) {return Plugin.wrap(target, this);}return target;}/*** mybatis配置的属性** @param properties mybatis配置的属性*/@Overridepublic void setProperties(Properties properties) {}
}

返回数据修改

比如根据部门id查询部门返回部门名称

import cn.hutool.core.util.StrUtil;
import io.swagger.annotations.ApiModel;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.util.*;import static com.disclosure.business.config.InitiateInterceptField.*;@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets" , args = {Statement.class})})
public class FillUserInterceptor implements Interceptor {private final String[] initiateInterceptField = {deptId, createBy, updateBy};public Object intercept(Invocation invocation) throws Throwable {MetaObject metaStatementHandler = SystemMetaObject.forObject(invocation.getTarget());MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("mappedStatement");if (!mappedStatement.getId().startsWith("com.disclosure.business")) return invocation.proceed();if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) return invocation.proceed();List<ResultMap> resultMaps = mappedStatement.getResultMaps();Class<?> resultType = resultMaps.get(0).getType();//稍微判断一下只有实体类又这个注解才进入下面if (!resultType.isAnnotationPresent(ApiModel.class)) {return invocation.proceed();}//获取mybatis返回的实体类类型名int resultMapCount = resultMaps.size();if (resultMapCount > 0) {Statement statement = (Statement) invocation.getArgs()[0];ResultSet resultSet = statement.getResultSet();if (resultSet != null) {//获得对应列名ResultSetMetaData resultSetMetaData = resultSet.getMetaData();List<String> columnList = new ArrayList<>();List<String> initiateInterceptFieldList = Arrays.asList(initiateInterceptField);for (int i = 1; i <= resultSetMetaData.getColumnCount(); i++) {//做下划线转驼峰操作columnList.add(StrUtil.toCamelCase(resultSetMetaData.getColumnName(i)));}initiateInterceptFieldList.retainAll(columnList);List<LinkedHashMap<String, Object>> resList = new ArrayList<>();while (resultSet.next()) {LinkedHashMap<String, Object> resultMap = new LinkedHashMap<>();for (String colName : columnList) {resultMap.put(colName, resultSet.getString(StrUtil.toUnderlineCase(colName)));}
//                    将转换后的map转换为实体类中,适用于实体类中有需要填充的数据
//                    Object o = resultType.newInstance();
//                    BeanUtils.populate(o, resultMap);resultMap.put(deptName, "测试部门");resultMap.put(createName, "测试新增人员");resultMap.put(updateName, "测试修改人员");resList.add(resultMap);}return resList;}}return invocation.proceed();}public Object plugin(Object target) {
//        System.out.println("将要包装的目标对象:" + target);return Plugin.wrap(target, this);}public void setProperties(Properties properties) {}
}

相关文章:

java 对mybatis拦截Interceptor进行权限控制(条件修改),入参修改,返回修改

使用jsqlparser对sql解析拼装 基础准备 <dependency><groupId>com.github.jsqlparser</groupId><artifactId>jsqlparser</artifactId><version>4.5</version><scope>compile</scope> </dependency> <depende…...

鲲鹏+麒麟v10 sp1 打包tomcat

一、安装打包软件 yum install rpm-build yum install rpmdevtools 二、复制tar包 注意&#xff1a;如果要修改配置文件需要把tar包解压&#xff0c;修改tomcat配置文件 cp apache-tomcat-8.5.93.tar.gz /root/rpm-build/SOURCES 三、编辑/root/rpm-build/SOURCES/tomcat.…...

大屏大概是怎么个开发法(前端)

写在前面&#xff0c;博主是个在北京打拼的码农&#xff0c;从事前端工作5年了&#xff0c;做过十多个大大小小不同类型的项目&#xff0c;最近心血来潮在这儿写点东西&#xff0c;欢迎大家多多指教。 对于文章中出现的任何错误请大家批评指出&#xff0c;一定及时修改。有任何…...

什么是Selenium?使用Selenium进行自动化测试!

你知道什么是 Selenium 吗&#xff1f;你知道为什么要使用它吗&#xff1f;答案就在本文中&#xff0c;很高兴能够与你共飧。 自动化测试正席卷全球&#xff0c;Selenium 认证是业界最抢手的技能之一。 什么是 Selenium&#xff1f; Selenium 是一种开源工具&#xff0c;用于…...

第一章:最新版零基础学习 PYTHON 教程(第十七节 - Python 表达式语句–Python返回语句)

return 语句用于结束函数调用的执行,并将结果(return 关键字后面的表达式的值)“返回”给调用者。return 语句之后的语句不被执行。如果return语句没有任何表达式,则返回特殊值None。return 语句总体上用于调用函数,以便可以执行传递的语句。 注意: Return 语句不能在函数…...

前端碎知识点

1 统计字符串中每个字符出现的频率 const str fgasdfadfdasd;const result str.split().reduce((a, b) > {if (a[b]) {a[b];} else {a[b] 1;}return a; }, {});const resultNew str.split().reduce((a, b) > {// undefined&#xff0c;就是NaNa[b] || (a[b] 1);ret…...

【Leetcode Sheet】Weekly Practice 7

Leetcode Test 1462 课程表Ⅳ(9.12) 你总共需要上 numCourses 门课&#xff0c;课程编号依次为 0 到 numCourses-1 。你会得到一个数组 prerequisite &#xff0c;其中 prerequisites[i] [ai, bi] 表示如果你想选 bi 课程&#xff0c;你 必须 先选 ai 课程。 有的课会有直接…...

leetcode Top100(23)回文链表

给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true采用动态数组&#xff0c;判断数组对称就可以了&#xff08;这解法空间…...

WebGL绘制圆形的点

目录 前言 如何实现圆形的点&#xff1f; 片元着色器内置变量&#xff08;gl_FragCoord、gl_PointCoord&#xff09; gl_PointCoord的含义 示例程序&#xff08;RoundedPoint.js&#xff09; 代码详解 前言 本文将讨论示例程序RoundedPoint&#xff0c;该程序绘制了圆…...

《The Rise and Potential of Large Language Model Based Agents: A Survey》全文翻译

The Rise and Potential of Large Language Model Based Agents: A Surve - 基于 LLMs 的代理的兴起和潜力&#xff1a;一项调查 论文信息摘要1. 介绍2. 背景2.1 AI 代理的起源2.2 代理研究的技术趋势2.3 为什么大语言模型适合作为代理大脑的主要组件 论文信息 题目&#xff1…...

在线地图获取城市路网数据

在线地图获取城市路网数据 近期科研项目中&#xff0c;需要获取城市路网数据&#xff0c;于是详细阅读各大在线地图api接口&#xff0c;总结出来这么一条可行的思路&#xff1a; 首先获取城市轮廓根据城市轮廓把城市分割成若干个小块在每个小块中根据在线地图的POI检索接口&a…...

8.2 Jmeter if控制器使用

前提&#xff1a;jmeter脚本需要用到if控制器&#xff0c;if判断如果查询不到&#xff0c;则去新增。 1、添加if控制器 线程组-->逻辑控制器-->如果(if)控制器 1&#xff09;、Expression (must evaluate to true or false) &#xff1a;表达式&#xff08;值必须是tru…...

科技云报道:青云科技打出“AI算力牌”,抢跑“云+AI”新增市场

科技云报道原创。 近三年&#xff0c;中国云计算市场在多个维度同时发生着剧烈变化——疫情极大加速了全社会对于数字化的认知和接受程度&#xff1b;一系列云原生技术依托着开源和蓬勃的市场而迅速发展演变&#xff0c;更多产品和技术名词同时涌向市场&#xff1b;国际关系复…...

学习路之PHP--lumen安装配置

一、下载lumen源码 composer create-project --prefer-dist laravel/lumen blog 安装lumen-generator composer require flipbox/lumen-generator 二、配置 bootstrap\app.php 97行 $app->register(Flipbox\LumenGenerator\LumenGeneratorServiceProvider::class);三、生成…...

【C++】构造函数和析构函数第一部分(构造函数和析构函数的作用)--- 2023.9.25

目录 前言初始化和清理的概念构造函数和析构函数的作用构造函数的作用析构函数的作用 使用构造函数和析构函数的注意事项默认的构造函数和析构函数结束语 前言 在使用c语言开发的项目场景中&#xff0c;我们往往会遇到申请空间的需求&#xff0c;同时也肯定遇到过程序运行一段…...

CocosCreator3.8研究笔记(二十一)CocosCreator Tween系统理解

在 Cocos Creator 3.x 版本中&#xff0c; Tween系统代替了原来的Action系统。很多朋友不明白Tween到底是什么&#xff0c;Tween原理是什么&#xff1f;怎么使用Tween&#xff1f; 今天就来详细了解一下&#xff0c;希望能帮助到大家加深对Tween的了解&#xff0c;并快速掌握Tw…...

大数据学习-目录

学习内容持续更新ing 1.大数据学习1.0-Centos8虚拟机安装 大数据学习1.0-Centos8虚拟机安装_汉卿HanQ的博客-CSDN博客 2.大数据学习1.1-Centos8网络配置 大数据学习1.1-Centos8网络配置_汉卿HanQ的博客-CSDN博客 3.大数据学习1.2-yum配置 大数据学习1.2-yum配置_汉卿HanQ的…...

《动手学深度学习 Pytorch版》 7.5 批量规范化

7.5.1 训练深层网络 训练神经网络的实际问题&#xff1a; 数据预处理的方式会对最终结果产生巨大影响。 训练时&#xff0c;多层感知机的中间层变量可能具有更广的变化范围。 更深层的网络很复杂容易过拟合。 批量规范化对小批量的大小有要求&#xff0c;只有批量大小足够…...

Toaster - Android 吐司框架,专治 Toast 各种疑难杂症

官网 https://github.com/getActivity/Toaster 这可能是性能优、使用简单&#xff0c;支持自定义&#xff0c;不需要通知栏权限的吐司 想了解实现原理的可以点击此链接查看&#xff1a;Toaster 源码 集成步骤 如果你的项目 Gradle 配置是在 7.0 以下&#xff0c;需要在 bui…...

2023年9月26日,历史上的今天大事件早读

1620年9月26日大明皇帝朱常洛驾崩 1815年9月26日俄、普、奥三国在巴黎发表缔结“神圣同盟” 1841年9月26日清代思想家、诗人龚自珍逝世 1849年9月26日“生理学之父”巴甫洛夫诞生 1909年9月26日云南陆军讲武堂创办 1953年9月26日画家徐悲鸿逝世 1980年9月26日国际宇航联合…...

基于MCP协议构建AI支付网关:连接Clawd与智能体的实践指南

1. 项目概述&#xff1a;一个连接Clawd与MCP的支付网关 最近在折腾一个很有意思的开源项目&#xff0c;叫 clawdpay-mcp 。这个项目在GitHub上由 Rishab87 维护&#xff0c;乍一看名字有点拗口&#xff0c;但拆解一下就能明白它的核心价值&#xff1a; clawdpay 和 M…...

从ChatGPT插件到自主Agent工作流:2026年AI工具栈跃迁的4个关键断点及突破路径

更多请点击&#xff1a; https://codechina.net 第一章&#xff1a;2026年AI工具栈搭建完整指南 构建面向生产环境的AI工具栈&#xff0c;需兼顾前沿性、稳定性与可扩展性。2026年主流实践已从单点模型调用转向模块化、可观测、可编排的智能工作流基础设施。以下为推荐技术选型…...

Glovebox:本地开发环境编排工具,解决混合环境管理与服务依赖难题

1. 项目概述&#xff1a;一个被低估的本地开发利器如果你是一名开发者&#xff0c;尤其是经常在本地搭建各种服务、测试不同技术栈的工程师&#xff0c;那么你一定对“环境配置”这件事深恶痛绝。每次新开一个项目&#xff0c;都要重复安装依赖、配置数据库、设置网络端口、处理…...

如何5分钟搭建个人离线小说库:番茄小说下载器终极指南

如何5分钟搭建个人离线小说库&#xff1a;番茄小说下载器终极指南 【免费下载链接】fanqienovel-downloader 下载番茄小说 项目地址: https://gitcode.com/gh_mirrors/fa/fanqienovel-downloader 你是否曾经遇到过这样的情况&#xff1a;在通勤路上网络突然中断&#xf…...

深入电机FOC内核:为什么`id=0`控制是能效与性能的黄金法则?(从方程到代码实现)

深入电机FOC内核&#xff1a;为什么id0控制是能效与性能的黄金法则&#xff1f; 在永磁同步电机&#xff08;PMSM&#xff09;的矢量控制领域&#xff0c;id0控制策略如同一位低调的幕后英雄。它不像那些复杂的算法那样引人注目&#xff0c;却在无数工业应用中默默发挥着关键作…...

求职自动化工具箱:用爬虫与状态机构建高效求职管理系统

1. 项目概述&#xff1a;一个求职者的工具箱如果你正在找工作&#xff0c;或者未来某天需要找工作&#xff0c;你大概率会和我有一样的感受&#xff1a;整个过程繁琐、重复&#xff0c;且充满不确定性。从海投简历到追踪进度&#xff0c;从准备面试到复盘总结&#xff0c;每个环…...

免费高效获取通达信金融数据:MOOTDX量化投资接口终极指南

免费高效获取通达信金融数据&#xff1a;MOOTDX量化投资接口终极指南 【免费下载链接】mootdx 通达信数据读取的一个简便使用封装 项目地址: https://gitcode.com/GitHub_Trending/mo/mootdx 想要快速获取A股市场数据却苦于高昂的API费用&#xff1f;MOOTDX是你的完美解…...

如何用OpenCore Legacy Patcher让老旧Mac焕发新生:5分钟快速上手指南

如何用OpenCore Legacy Patcher让老旧Mac焕发新生&#xff1a;5分钟快速上手指南 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 还在为你的老旧Mac无法升级到…...

如何用Python爬虫将知识星球内容制作成PDF电子书:完整指南

如何用Python爬虫将知识星球内容制作成PDF电子书&#xff1a;完整指南 【免费下载链接】zsxq-spider 爬取知识星球内容&#xff0c;并制作 PDF 电子书。 项目地址: https://gitcode.com/gh_mirrors/zs/zsxq-spider 知识星球作为优质内容社区&#xff0c;汇集了大量付费专…...

别再死记硬背了!用Python模拟超前进位加法器,直观理解其速度优势

用Python模拟超前进位加法器&#xff1a;从硬件原理到算法思维的跨越 在计算机科学和电子工程交叉领域&#xff0c;加法器是最基础却又最精妙的设计之一。传统教学中&#xff0c;我们往往通过抽象的电路图来理解超前进位加法器&#xff08;CLA&#xff09;的速度优势&#xff0…...