利用MyBatis的动态SQL特性抽象统一SQL查询接口

1. SQL查询的统一抽象

 MyBatis制动动态SQL的构造,利用动态SQL和自定义的参数Bean抽象,可以将绝大部分SQL查询抽象为一个统一接口,查询参数使用一个自定义bean继承Map,使用映射的方法构造多查询参数.在遇到多属性参数(例如order by,其参数包括列名,升序降序类型,以及可以多个列及升降序类型凭借在order by之后)无法使用简单的key-value表示时,可以将参数单独抽象为一个类.

将要用到的bean

package com.xxx.mybatistask.bean;import com.xxx.mybatistask.support.jsonSerializer.JsonDateDeserializer;
import com.xxx.mybatistask.support.jsonSerializer.JsonDateSerializer;
import org.codehaus.jackson.map.annotate.JsonDeserialize;
import org.codehaus.jackson.map.annotate.JsonSerialize;import java.util.Date;public class Post {private int id;private String title;private String content;private String author;private PostStatus status;private Date created;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}public String getAuthor() {return author;}public void setAuthor(String author) {this.author = author;}public PostStatus getStatus() {return status;}public void setStatus(PostStatus status) {this.status = status;}@JsonSerialize(using = JsonDateSerializer.class)public Date getCreated() {return created;}@JsonDeserialize(using = JsonDateDeserializer.class)public void setCreated(Date created) {this.created = created;}
}

 

 

1)参数Bean设计

总的参数Map抽象接口设计

package com.xxx.mybatistask.bean.query;import java.util.Map;public interface QueryParam extends Map<String, Object> {/*** 新增查询参数** @param key   参数名* @param value 参数值* @return*/QueryParam fill(String key, Object value);
}

 

列表查询参数接口

package com.xxx.mybatistask.bean.query;import java.util.List;public interface ListQueryParam extends QueryParam {/*** 获取排序条件集合** @return*/List<SortCond> getSortCond();/*** 添加排序条件** @param sortCond*/void addSortCond(SortCond sortCond);void addSortCond(List<SortCond> sortCondList);/*** 获取当前页数** @return*/Integer getPage();/*** 获取每页查询记录数** @return*/Integer getPageSize();/*** 设置当前页数*/void setPage(Integer page);/*** 设置每页查询记录数*/void setPageSize(Integer pageSize);
}

 

列表查询参数接口实现

package com.xxx.mybatistask.bean.query;import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;public class GenericQueryParam extends LinkedHashMap<String, Object> implements ListQueryParam {/*** 最大单页记录数*/public final static int MAX_PAGE_SIZE = 100;/*** 当前页面key*/private final static String PAGE_KEY = "__page";/*** 单页记录数key*/private final static String PAGESIZE_KEY = "__pagesize";/*** 排序参数List key*/private final static String SORTCOND_KEY = "__sortcond";public GenericQueryParam() {this(1, 10);}public GenericQueryParam(Integer page,Integer pageSize) {setPage(page);setPageSize(pageSize);}@Overridepublic Integer getPage() {return (Integer) get(PAGE_KEY);}@Overridepublic Integer getPageSize() {return (Integer) get(PAGESIZE_KEY);}@Overridepublic void setPage(Integer page) {put(PAGE_KEY, page);}@Overridepublic void setPageSize(Integer pageSize) {put(PAGESIZE_KEY, pageSize);}@Override@SuppressWarnings("unchecked")public List<SortCond> getSortCond() {List<SortCond> sortCondList = (List<SortCond>) get(SORTCOND_KEY);if (sortCondList == null) {sortCondList = new LinkedList<SortCond>();put(SORTCOND_KEY, sortCondList);}return sortCondList;}@Override@SuppressWarnings("unchecked")public void addSortCond(SortCond sortCond) {List<SortCond> sortCondList = (List<SortCond>) get(SORTCOND_KEY);if (sortCondList == null) {sortCondList = new LinkedList<SortCond>();put(SORTCOND_KEY, sortCondList);}sortCondList.add(sortCond);}@Overridepublic void addSortCond(List<SortCond> sortCondList) {for (SortCond sortCond : sortCondList) addSortCond(sortCond);}@Overridepublic QueryParam fill(String key, Object value) {put(key, value);return this;}
}

 

 

排序参数的抽象

package com.xxx.mybatistask.bean.query;public class SortCond {/*** 排序类型枚举*/public enum Order {ASC, DESC}/*** 排序类型*/private String column;/*** 排序类型*/private Order order;public SortCond(String column) {this(column, Order.DESC);}public SortCond(String column, Order order) {this.column = column;this.order = order;}public String getColumn() {return column;}public Order getOrder() {return order;}
}

 

 

2)Service查询接口设计

package com.xxx.mybatistask.service;import com.xxx.mybatistask.bean.query.GenericQueryParam;
import org.apache.ibatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import javax.annotation.Resource;public abstract class AbstractService {protected final Logger logger = LoggerFactory.getLogger(getClass());@Resourceprotected SqlSession sqlSession;/*** 分页参数校验** @param params* @param rowCount* @return*/protected void pageParamValidate(GenericQueryParam params, int rowCount) {int page = params.getPage();int pageSize = params.getPageSize();if (page < 1) page = 1;if (pageSize < 1) pageSize = 1;if (pageSize > GenericQueryParam.MAX_PAGE_SIZE)pageSize = GenericQueryParam.MAX_PAGE_SIZE;int maxPage = (int) Math.ceil((double) rowCount / pageSize);if (page > maxPage) page = maxPage;params.setPage(page);params.setPageSize(pageSize);}
}

 

 

package com.xxx.mybatistask.service;import com.xxx.mybatistask.bean.Post;
import com.xxx.mybatistask.bean.query.GenericQueryParam;
import com.xxx.mybatistask.bean.query.ListResult;public interface PostService {/*** 查询参数列名枚举*/public enum PostQueryPram {title, content, author, status, created}void create(Post post);/*** 翻页查询** @param param* @return*/ListResult<Post> select(GenericQueryParam param);void update(Post post);
}

 

 

package com.xxx.mybatistask.service.impl;import com.xxx.mybatistask.bean.Post;
import com.xxx.mybatistask.bean.query.GenericQueryParam;
import com.xxx.mybatistask.bean.query.ListResult;
import com.xxx.mybatistask.service.AbstractService;
import com.xxx.mybatistask.service.PostService;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Service;import java.util.LinkedList;
import java.util.List;@Service
public class PostServiceImpl extends AbstractService implements PostService {@Overridepublic void create(Post post) {sqlSession.insert("post.insert", post);}@Overridepublic ListResult<Post> select(GenericQueryParam params) {Integer rowCount = sqlSession.selectOne("post.selectCount", params);if (rowCount == 0) {return new ListResult<Post>(new LinkedList<Post>(), 0);}// 分页参数检查
        pageParamValidate(params, rowCount);int page = params.getPage();int pageSize = params.getPageSize();int offset = (page - 1) * pageSize;RowBounds rowBounds = new RowBounds(offset, pageSize);List<Post> postList = sqlSession.selectList("post.select", params, rowBounds);return new ListResult<Post>(postList, rowCount);}@Overridepublic void update(Post post) {sqlSession.update("post.update", post);}
}

 

 

 

3)自定义参数bean的解析与转换

以SortCond为例,由于是多属性查询参数,所以我们需要自己定义参数在客户端的文本格式,从客户端传入后再使用自定义的Paser来将其包装成SortCond

例如此处我们定义的排序参数在url中的格式为

/api/post/query/title/an?page=3&pageSize=200&sorts=created:DESC|author:ASC

其中排序参数为 "created:DESC|author:ASC" , 解析类如下

package com.xxx.mybatistask.support.stringparser;import java.util.List;public interface Parser<T> {/*** 字符串转对象** @param parseString 待转换字符串* @return List<T>  转换完成的对象List*/List<T> parseList(String parseString);
}

 

package com.xxx.mybatistask.support.stringparser;import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.xxx.mybatistask.bean.query.SortCond;import java.util.List;
import java.util.Map;public class SortCondParser implements Parser<SortCond> {/*** 排序列分隔符*/private static final String COL_SPLITTER = "|";/*** 顺序类型分隔符*/private static final String ORDER_SPLITTER = ":";/*** 列名检查*/private Class<? extends Enum> columnEnumCls;public SortCondParser(Class<? extends Enum> columnEnumCls) {this.columnEnumCls = columnEnumCls;}/*** 将字符串转换为SortCond* 字符串的标准格式为* title:ASC|created:DESC** @param parseString 待转换字符串* @return*/@Overridepublic List<SortCond> parseList(String parseString) {List<SortCond> sortCondList = Lists.newArrayList();// 将字符串切分为 {"column" => "order"} 的形式Map<String, String> sortOrderMap =Splitter.on(COL_SPLITTER).trimResults().omitEmptyStrings().withKeyValueSeparator(ORDER_SPLITTER).split(parseString);String column = null;String order = null;for (Map.Entry<String, String> entry : sortOrderMap.entrySet()) {// 验证column合法性column = entry.getKey();if (column != null && !column.equals("")) {Enum.valueOf(columnEnumCls, column);} else {break;}// 验证order合法性order = entry.getValue();if (order != null && !order.equals("")) {Enum.valueOf(SortCond.Order.class, order);} else {order = SortCond.Order.DESC.name();}sortCondList.add(new SortCond(column, SortCond.Order.valueOf(order)));}return sortCondList;}
}

 

 

4) 动态查询SQL的编写

<select id="select"parameterType="com.xxx.mybatistask.bean.query.GenericQueryParam"resultType="com.xxx.mybatistask.bean.Post"><![CDATA[selectid,title,content,author,status,createdfrompost]]><where><if test="id != null">and id = #{id}</if><if test="title != null and title != ''">and title like concat('%', #{title}, '%')</if><if test="author != null and author != ''">and author like concat('%', #{author}, '%')</if><if test="content != null and content != ''">and match(content) against(#{content})</if><if test="status != null">and status = #{status}</if><if test="created != null and created != ''">and created = #{created}</if></where><if test="_parameter.getSortCond().size() != 0">order by<foreach collection="_parameter.getSortCond()" item="sortCond" separator=",">${sortCond.column} ${sortCond.order}</foreach></if></select>

 

至此SQL抽象接口以及完成,结合SortCond类,动态SQL和OGNL动态生成了order by参数,而类似的像 JOIN ... ON (USING) 或者 GROUP BY ... HAVING 等查询参数条件,也可以将其抽象成bean,通过GenericQueryParam成员变量的形式拼接到SQL查询语句中来

另外代码中并没有对参数进行过多的检查,原因是:

1. MyBatis SQL查询使用prepareStatement,对于注入问题相对安全

2. 动态SQL查询使用<if>判断where查询条件,如果参数中的map key不是有效列名,将不会拼接到SQL语句中

3. 即使由于恶意用户篡改参数格式造成不规范参数的SQL查询异常,对于这种异常只需要重定向到全局error页面即可

 

5) Controller调用示例

@RequestMapping(value = "/query/{colKey}/{colVal}", method = RequestMethod.GET)public@ResponseBodyObject query(@PathVariable String colKey,@PathVariable String colVal,@RequestParam(value = "status", required = false) String status,@RequestParam(value = "page", required = false, defaultValue = "1") Integer page,@RequestParam(value = "pageSize", required = false, defaultValue = "10") Integer pageSize,@RequestParam(value = "sorts", required = false, defaultValue = "") String sorts) {// page and colGenericQueryParam params = new GenericQueryParam(page, pageSize);params.fill(colKey, colVal).fill(PostService.PostQueryPram.status.name(),PostStatus.valueOf(status));// sortsSortCondParser sortCondParser = new SortCondParser(PostService.PostQueryPram.class);params.addSortCond(sortCondParser.parseList(sorts));ListResult<Post> postList = postService.select(params);return dataJson(postList);}

 

 

 

 

2. TypeHandler设计

上文中的bean Post类中status属性类型是enum类,如下

package com.xxx.mybatistask.bean;public enum PostStatus {NORMAL(0, "正常"), LOCKED(1, "锁定");private int code;private String text;private PostStatus(int code, String text) {this.code = code;this.text = text;}public int code() {return code;}public String text() {return text;}public static PostStatus codeOf(int code) {for (PostStatus postStatus : PostStatus.values()) {if (postStatus.code == code) {return postStatus;}}throw new IllegalArgumentException("invalid code");}public static boolean contains(String text) {for (PostStatus postStatus : PostStatus.values()) {if (postStatus.toString().equals(text)) {return true;}}return false;}
}

 

而这个属性在数据库中的类型实际上市一个tinyint表示的标记位,为了让mybatis jdbc自动转换这个tinyint标记位为enum(查询时)和转换enum为tinyint(插入更新时),需要编写mybatis typehandler

package com.xxx.mybatistask.support.typehandler;import com.xxx.mybatistask.bean.PostStatus;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;public class PostStatusTypeHandler implements TypeHandler<PostStatus> {/*** PostStatus插入数据库时转换的方法* 将使用PostStatus的code插入数据库** @param preparedStatement* @param index* @param postStatus* @param jdbcType* @throws SQLException*/@Overridepublic void setParameter(PreparedStatement preparedStatement, int index, PostStatus postStatus, JdbcType jdbcType) throws SQLException {preparedStatement.setInt(index, postStatus.code());}/*** status查询出来时转为PostStatus的方法** @param resultSet* @param colName* @return* @throws SQLException*/@Overridepublic PostStatus getResult(ResultSet resultSet, String colName) throws SQLException {return PostStatus.codeOf(resultSet.getInt(colName));}@Overridepublic PostStatus getResult(ResultSet resultSet, int colIndex) throws SQLException {return PostStatus.codeOf(resultSet.getInt(colIndex));}@Overridepublic PostStatus getResult(CallableStatement callableStatement, int colIndex) throws SQLException {return PostStatus.codeOf(callableStatement.getInt(colIndex));}
}

在MyBatis配置文件中配置这个TypeHandler是其对PostStatus参数生效

    <typeHandlers><typeHandler handler="com.xxx.mybatistask.support.typehandler.PostStatusTypeHandler"javaType="com.xxx.mybatistask.bean.PostStatus"/></typeHandlers>

 

 

3. 特殊参数的序列化与反序列化

由于需要实现接收和响应JSON数据,自动将JSON数据包装为具体对象类,此处使用了Spring的@ResponseBody以及@RequestBody标签,JSON的转换器为org.codehaus.jackson

但是对于某些特殊属性,例如此处的Post里的created属性,在bean中表现为Date类型,而在数据库中为TIMESTAMP类型,如果直接输出到JSON响应中,将会输出timestamp的毫秒数,为了格式化为自定义的格式,我们需要自定义一个JSON序列化(转为响应文本时)与反序列化(接收请求参数转为POST类时)的类.如下

序列化类

package com.xxx.mybatistask.support.jsonSerializer;import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;public class JsonDateSerializer extends JsonSerializer<Date> {private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");@Overridepublic void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {jsonGenerator.writeString(sdf.format(date));}
}

 

反序列化类

package com.xxx.mybatistask.support.jsonSerializer;import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.ObjectCodec;
import org.codehaus.jackson.map.DeserializationContext;
import org.codehaus.jackson.map.JsonDeserializer;import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;public class JsonDateDeserializer extends JsonDeserializer<Date> {private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");@Overridepublic Date deserialize(JsonParser jsonParser,DeserializationContext deserializationContext)throws IOException {ObjectCodec oc = jsonParser.getCodec();JsonNode node = oc.readTree(jsonParser);try {return sdf.parse(node.getTextValue());} catch (ParseException e) {e.printStackTrace();}return null;}
}

 

然后注意在Post类中标明,当Jackson序列化Post类为JSON串或将JSON串反序列化成Post类时,将调用这两个类,Post类的代码片段

    @JsonSerialize(using = JsonDateSerializer.class)public Date getCreated() {return created;}@JsonDeserialize(using = JsonDateDeserializer.class)public void setCreated(Date created) {this.created = created;}

 

 

THE END

转载于:https://www.cnblogs.com/zemliu/p/3248171.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/377972.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ctype函数_PHP Ctype(字符类型)函数

ctype函数Ctype功能 (Ctype functions) PHP provides some of the built-in functions, which are used to verify the characters in the string i.e. to check whether a string contains the characters of specific types. PHP提供了一些内置函数&#xff0c;这些函数用于验…

Linux 平台下 MySQL 5.5 安装 说明 与 示例

一.下载说明前期的一些准备说明&#xff0c;参考&#xff1a;MySQL 发展史http://blog.csdn.net/tianlesoftware/article/details/6999245Mysql 不同版本 说明http://blog.csdn.net/tianlesoftware/article/details/6723117 MySQL 分为Community Server 和 Enterprise Edition。…

开始python之旅

接触python缘于工作所需&#xff0c;曾经接触过C、C等语言&#xff0c;对于编程语言在学习上大体是一个套路&#xff0c;当然套路因人而异&#xff0c;适合就好。接下来&#xff0c;我将不断分享python的知识和学习技巧&#xff0c;共同学习。 起源 初识一门语言善于先了解语言…

LeetCode 112. 路径总和 、113. 路径总和 II 思考分析

目录112. 路径总和题目递归解递归解&#xff0c;其他人的解法迭代解&#xff0c;其他人的解法113. 路径总和 II题目递归解递归解&#xff0c;参考别人的思路112. 路径总和 题目 给定一个二叉树和一个目标和&#xff0c;判断该树中是否存在根节点到叶子节点的路径&#xff0c;…

kotlin 查找id_Kotlin程序查找矩阵的转置

kotlin 查找idA transpose of a matrix is simply a flipped version of the original matrix. We can transpose a matrix by switching its rows with its columns 矩阵的转置只是原始矩阵的翻转形式。 我们可以通过切换矩阵的行和列来转置矩阵 Given a matrix, we have to…

[mongodb翻译]分片和故障转移

一个配置恰当的mongodb 分片集群不会有单点失效。 本章节描述了集群服务器中可能出现的故障&#xff0c;及相应的对策。 1. 某个mongos路由进程故障 每一个mongos会运行每一台应用服务器上面&#xff0c;该应用服务器只能通过这个mongos进程和集群进行通信。mongos进程不是…

看张子阳的书真是收获很多,也醒悟了很多(一)

摘录&#xff1a; 这是有一次开会时&#xff0c;我的老总跟我们说了这样一个事例&#xff1a;通常来说&#xff0c;医生是很高尚的职业&#xff08;暂不考虑国内医生的负面新闻&#xff09;&#xff0c;尤其是牙科医生&#xff0c; 他们有着体面的工作并且收入不菲。但是&#…

【C++ grammar】抽象、封装与this指针

目录1、Abstraction and Encapsulation&#xff08;抽象与封装&#xff09;1. Data Field Encapsulation (数据域封装)2. Accessor and Mutator (访问器与更改器)2.1. To read/write private data, we need get/set function (为读写私有数据&#xff0c;需要get/set函数)2.2. …

java创建临时文件_用Java创建一个临时文件

java创建临时文件The task is to create a temporary file in Java. 任务是用Java创建一个临时文件。 Creating a temporary file 创建一个临时文件 To create a temporary file in java – we use createTempFile() method of "File" class. The createTempFile()…

十九、图像的形态学操作

一、图像形态学 图像形态学是图像处理学科的一个单独分支学科 主要针对的是灰度图和二值图像 是由数学的集合论以及数学中的拓扑几何原理发展而来 二、膨胀操作&#xff08;dilate&#xff09; 33的卷积核 以33为卷积核从左往右(从上往下)开始运行&#xff0c;若这卷积核…

X名称空间(WPF)

笔记简述 闲话x名称空间简要x名称空间的Attributex名称空间的标签扩展x名称空间的XAML指令元素闲话 本笔记参考与《深入浅出WPF》、MSDN、Some Blog… MSDN的飞机票点这里。 x名称空间简要 在VS中新建个WpfApplication都会自动生成xmlns:x"http://schemas.microsoft.com/w…

基于Bresenham和DDA算法画线段

直线&#xff1a;ykxb 为了将他在显示屏上显示出来&#xff0c;我们需要为相应的点赋值&#xff0c;那么考虑到计算机的乘法执行效率&#xff0c;我们肯定不会选择用Ykxb这个表达式求值&#xff0c;然后进行画线段。 我们应当是将它转化为加法运算。 下面提供两种常见的算法&am…

leetcode 106. 从中序与后序遍历序列构造二叉树 105. 从前序与中序遍历序列构造二叉树思考分析

目录1、106题目2、参考思路&#xff1a;递归切割数组3、105题目4、同样思路的代码1、106题目 2、参考思路&#xff1a;递归切割数组 代码参考&#xff1a;公众号&#xff1a;代码随想录 后序数组中序数组 以 后序数组(左右中)的最后一个元素作为切割点&#xff0c;先切中序数组…

按频率对元素进行排序

Prerequisite: 先决条件&#xff1a; Hashing data structure 散列数据结构 How to write user-defined comparator for priority queue STL in C? 如何在C 中为优先级队列STL编写用户定义的比较器&#xff1f; How to sort a map based on values instead of value? 如何根…

二十、分水岭算法

一、基本原理 分水岭算法主要是基于距离变换&#xff08;distance transform&#xff09;&#xff0c;找到mark一些种子点&#xff0c;从这些种子点出发根据像素梯度变化进行寻找边缘并标记 分水岭&#xff1a;可以简单的理解成一座山&#xff0c;然后来洪水了&#xff0c;水开…

细数WOW里暴雪的“亲儿子”们

. 不知何时&#xff0c;魔兽世界的词汇中忽然出现了一个新玩意&#xff1a;亲儿子。虽说这个称呼现在大多是拿来调侃法爷的&#xff0c;但江山代有儿子出&#xff0c;各领风骚一两天&#xff0c;今天风光无限的法爷们也经历过被其他职业压得抬不起头的小媳妇生涯。那么今天…

Linux下串口ttyS2,ttyS3不能用的问题解决办法

PC104&#xff0c;Xlinux下&#xff0c;突然发现串口3,4不能用。。。 以为是硬件的问题&#xff0c;换成wince后&#xff0c;3,4工作正常&#xff0c;排除电路问题 在linux下查看dmesg: serial8250: ttyS0 at I/O 0x3f8 (irq 4) is a 16550Aserial8250: ttyS1 at I/O 0x2f8 (i…

安卓log.e函数打印示例_log1p()函数以及C ++中的示例

安卓log.e函数打印示例C log1p()函数 (C log1p() function) log1p() function is a library function of cmath header, it is used to get the natural logarithm (the base-e logarithm) of the one plus given value. It accepts a value (float, double, or long double) …

【C++grammar】C++类数据成员的初始化

目录1、类成员的就地初始化example2、构造函数初始化3、默认构造函数&#xff1a;Default Constructor4、举例5、成员的初始化方法的优先级1、类成员的就地初始化example class S { int m 7; // ok, copy-initializes m int n(7); // 错误&#xff1a;不允许用小括号初始化…

二十一、人脸检测

一、识别图像中的人脸 haarcascade_frontalface_alt_tree.xml lbpcascade_frontalcatface.xml GitHub上有Haar级联检测器源代码可自行下载&#xff0c;lbp级联检测器也一样有源码可自行下载 也一样 import cv2 as cv import numpy as npdef face_detect(image):gray cv.cvtC…