当前位置: 代码网 > it编程>编程语言>Java > MyBatis映射器模块最佳实践

MyBatis映射器模块最佳实践

2026年01月12日 Java 我要评论
一、mybatis整体架构与映射器模块在深入映射器模块之前,我们先了解mybatis的整体架构,以及映射器模块在其中的重要地位。从架构图可以看出,mybatis采用了分层架构设计,而映射器模块(map

一、mybatis整体架构与映射器模块

在深入映射器模块之前,我们先了解mybatis的整体架构,以及映射器模块在其中的重要地位。

从架构图可以看出,mybatis采用了分层架构设计,而映射器模块(mapper interface)位于接口层,是用户与mybatis交互的主要入口。它通过接口定义数据库操作,结合xml或注解配置sql语句,利用动态代理技术自动实现接口,让开发者以面向对象的方式操作数据库。

1.1 映射器模块的核心职责

映射器模块主要承担以下核心职责:
定义数据库操作接口 - 通过java接口定义crud操作
sql语句映射 - 将接口方法与sql语句关联
参数映射 - 将方法参数转换为sql参数
结果映射 - 将查询结果映射为java对象
动态代理实现 - 自动生成接口实现类

1.2 为什么需要mapper?

传统的jdbc编程存在以下问题:

//传统jdbc方式
string sql = "select * from t_user where id = ?";
preparedstatement stmt = conn.preparestatement(sql);
stmt.setint(1, userid);
resultset rs = stmt.executequery();
// 手动解析resultset并映射为对象...

这种方式的问题:
1、sql分散在代码中,难以维护
2、参数设置和结果映射都是手工操作
3、容易出现类型转换错误
4、代码重复度高

使用mapper后:

//mapper方式
@select("select * from t_user where id = #{id}")
user selectbyid(long id);
// 使用
user user = usermapper.selectbyid(1l);

优势:
sql与代码分离,易于维护
自动参数映射和结果映射
类型安全
代码简洁

1.3 mapper的使用方式

mybatis支持两种mapper配置方式:

二、mapper接口架构

mybatis的映射器模块采用了接口+配置的设计模式。

2.1 mapper接口定义

mapper是一个普通的java接口,无需实现类:

public interface usermapper {
    // 查询单个对象
    user selectbyid(long id);
    // 查询列表
    list<user> selectall();
    // 插入
    int insert(user user);
    // 更新
    int update(user user);
    // 删除
    int deletebyid(long id);
    // 复杂查询
    list<user> selectbycondition(@param("name") string name,
                                  @param("age") integer age);
}

2.2 mapper接口特点

无需实现类 - mybatis通过动态代理自动生成实现
方法名与sql id对应 - 接口方法名即为mappedstatement的id
参数灵活 - 支持单参数、多参数、对象参数
返回值多样 - 支持单对象、集合、map等

2.3 mapperregistry注册中心

mybatis维护了mapper接口的注册中心:

public class mapperregistry {
    // configuration对象
    private final configuration config;
    // mapper接口与代理工厂的映射
    private final map<class<?>, mapperproxyfactory<?>> knownmappers = 
        new hashmap<>();
    public mapperregistry(configuration config) {
        this.config = config;
    }
    // 添加mapper接口
    public <t> void addmapper(class<t> type) {
        if (type.isinterface()) {
            if (hasmapper(type)) {
                throw new bindingexception(
                    "type " + type + " is already known to the mapperregistry."
                );
            }
            boolean loadcompleted = false;
            try {
                // 创建mapperproxyfactory
                knownmappers.put(type, new mapperproxyfactory<>(type));
                // 解析mapper注解
                mapperannotationbuilder parser = 
                    new mapperannotationbuilder(config, type);
                parser.parse();
                loadcompleted = true;
            } finally {
                if (!loadcompleted) {
                    knownmappers.remove(type);
                }
            }
        }
    }
    // 获取mapper实例
    public <t> t getmapper(class<t> type, sqlsession sqlsession) {
        final mapperproxyfactory<t> mapperproxyfactory = 
            (mapperproxyfactory<t>) knownmappers.get(type);
        if (mapperproxyfactory == null) {
            throw new bindingexception(
                "type " + type + " is not known to the mapperregistry."
            );
        }
        try {
            return mapperproxyfactory.newinstance(sqlsession);
        } catch (exception e) {
            throw new bindingexception(
                "error getting mapper instance. cause: " + e, e
            );
        }
    }
    // 检查是否已注册
    public <t> boolean hasmapper(class<t> type) {
        return knownmappers.containskey(type);
    }
    // 获取所有mapper接口
    public collection<class<?>> getmappers() {
        return collections.unmodifiablecollection(knownmappers.keyset());
    }
}

三、sql语句映射

mybatis提供了灵活的sql映射方式。

3.1 xml配置方式

<?xml version="1.0" encoding="utf-8" ?>
<!doctype mapper public "-//mybatis.org//dtd mapper 3.0//en"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.usermapper">
    <!--结果映射 -->
    <resultmap id="baseresultmap" type="com.example.entity.user">
        <id column="id" property="id" jdbctype="bigint"/>
        <result column="name" property="name" jdbctype="varchar"/>
        <result column="email" property="email" jdbctype="varchar"/>
        <result column="age" property="age" jdbctype="integer"/>
        <result column="create_time" property="createtime" 
                jdbctype="timestamp"/>
    </resultmap>
    <!--查询单个用户 -->
    <select id="selectbyid" resultmap="baseresultmap">
        select id, name, email, age, create_time
        from t_user
        where id = #{id}
    </select>
    <!-- 查询所有用户 -->
    <select id="selectall" resultmap="baseresultmap">
        select id, name, email, age, create_time
        from t_user
        order by id
    </select>
    <!-- 插入用户 -->
    <insert id="insert" parametertype="com.example.entity.user" 
            usegeneratedkeys="true" keyproperty="id">
        insert into t_user (name, email, age, create_time)
        values (#{name}, #{email}, #{age}, now())
    </insert>
    <!--更新用户 -->
    <update id="update" parametertype="com.example.entity.user">
        update t_user
        set name = #{name},
            email = #{email},
            age = #{age}
        where id = #{id}
    </update>
    <!--删除用户 -->
    <delete id="deletebyid">
        delete from t_user
        where id = #{id}
    </delete>
    <!--动态sql查询 -->
    <select id="selectbycondition" resultmap="baseresultmap">
        select id, name, email, age, create_time
        from t_user
        <where>
            <if test="name != null and name != ''">
                and name like concat('%', #{name}, '%')
            </if>
            <if test="age != null">
                and age = #{age}
            </if>
        </where>
        order by id
    </select>
</mapper>

3.2 注解配置方式

public interface usermapper {
    @select("select * from t_user where id = #{id}")
    @results(id = "userresult", value = {
        @result(property = "id", column = "id", id = true),
        @result(property = "name", column = "name"),
        @result(property = "email", column = "email"),
        @result(property = "age", column = "age"),
        @result(property = "createtime", column = "create_time")
    })
    user selectbyid(long id);
    @select("select * from t_user order by id")
    list<user> selectall();
    @insert("insert into t_user (name, email, age, create_time) " +
            "values (#{name}, #{email}, #{age}, now())")
    @options(usegeneratedkeys = true, keyproperty = "id")
    int insert(user user);
    @update("update t_user set name = #{name}, email = #{email}, " +
            "age = #{age} where id = #{id}")
    int update(user user);
    @delete("delete from t_user where id = #{id}")
    int deletebyid(long id);
    @select("<script>" +
            "select * from t_user " +
            "<where>" +
            "<if test='name != null'>" +
            "and name like concat('%', #{name}, '%')" +
            "</if>" +
            "<if test='age != null'>and age = #{age}</if>" +
            "</where>" +
            "</script>")
    list<user> selectbycondition(@param("name") string name, 
                                  @param("age") integer age);
}

3.3 混合配置方式

xml和注解可以混合使用:

public interface usermapper {
    //注解方式:简单查询
    @select("select * from t_user where id = #{id}")
    user selectbyid(long id);
    //xml方式:复杂查询
    list<user> selectbycondition(userquery query);
}
<mapper namespace="com.example.mapper.usermapper">
    <!-- 复杂查询使用xml -->
    <select id="selectbycondition" resultmap="baseresultmap">
        select * from t_user
        <where>
            <if test="name != null">
                and name like concat('%', #{name}, '%')
            </if>
            <if test="age != null">
                and age = #{age}
            </if>
        </where>
    </select>
</mapper>

四、动态代理实现

mybatis通过jdk动态代理自动实现mapper接口。

4.1 mapperproxyfactory代理工厂

public class mapperproxyfactory<t> {
    // mapper接口类型
    private final class<t> mapperinterface;
    // 方法缓存
    private final map<method, mappermethod> methodcache = 
        new concurrenthashmap<>();
    public mapperproxyfactory(class<t> mapperinterface) {
        this.mapperinterface = mapperinterface;
    }
    //创建代理实例
    public t newinstance(sqlsession sqlsession) {
        final mapperproxy<t> mapperproxy = new mapperproxy<>(
            sqlsession, mapperinterface, methodcache
        );
        return newinstance(mapperproxy);
    }
    @suppresswarnings("unchecked")
    protected t newinstance(mapperproxy<t> mapperproxy) {
        // 使用jdk动态代理创建代理对象
        return (t) proxy.newproxyinstance(
            mapperinterface.getclassloader(),
            new class[]{mapperinterface},
            mapperproxy
        );
    }
    public class<t> getmapperinterface() {
        return mapperinterface;
    }
    public map<method, mappermethod> getmethodcache() {
        return methodcache;
    }
}

4.2 mapperproxy代理类

public class mapperproxy<t> implements invocationhandler {
    private final sqlsession sqlsession;
    private final class<t> mapperinterface;
    private final map<method, mappermethod> methodcache;
    public mapperproxy(sqlsession sqlsession, class<t> mapperinterface, 
                       map<method, mappermethod> methodcache) {
        this.sqlsession = sqlsession;
        this.mapperinterface = mapperinterface;
        this.methodcache = methodcache;
    }
    @override
    public object invoke(object proxy, method method, object[] args) 
        throws throwable {
        // 1.如果是object类的方法,直接执行
        if (object.class.equals(method.getdeclaringclass())) {
            try {
                return method.invoke(this, args);
            } catch (throwable t) {
                throw exceptionutil.unwrapthrowable(t);
            }
        }
        //2.获取mappermethod并执行
        final mappermethod mappermethod = cachedmappermethod(method);
        return mappermethod.execute(sqlsession, args);
    }
    //缓存mappermethod
    private mappermethod cachedmappermethod(method method) {
        return methodcache.computeifabsent(method, 
            k -> new mappermethod(mapperinterface, method, 
                                  sqlsession.getconfiguration())
        );
    }
}

4.3 mappermethod方法执行器

public class mappermethod {
    // sqlcommand封装了sql命令信息
    private final sqlcommand command;
    // methodsignature封装了方法签名信息
    private final methodsignature method;
    public mappermethod(class<?> mapperinterface, method method, 
                       configuration config) {
        this.command = new sqlcommand(config, mapperinterface, method);
        this.method = new methodsignature(config, mapperinterface, method);
    }
    public object execute(sqlsession sqlsession, object[] args) {
        object result;
        switch (command.gettype()) {
            case insert: {
                //插入操作
                object param = method.convertargstosqlcommandparam(args);
                result = rowcountresult(
                    sqlsession.insert(command.getname(), param)
                );
                break;
            }
            case update: {
                //更新操作
                object param = method.convertargstosqlcommandparam(args);
                result = rowcountresult(
                    sqlsession.update(command.getname(), param)
                );
                break;
            }
            case delete: {
                //删除操作
                object param = method.convertargstosqlcommandparam(args);
                result = rowcountresult(
                    sqlsession.delete(command.getname(), param)
                );
                break;
            }
            case select:
                if (method.returnsvoid() && method.hasresulthandler()) {
                    // 有resulthandler的查询
                    executewithresulthandler(sqlsession, args);
                    result = null;
                } else if (method.returnsmany()) {
                    //返回集合
                    result = executeformany(sqlsession, args);
                } else if (method.returnsmap()) {
                    //返回map
                    result = executeformap(sqlsession, args);
                } else if (method.returnscursor()) {
                    //返回cursor
                    result = executeforcursor(sqlsession, args);
                } else {
                    //返回单个对象
                    object param = method.convertargstosqlcommandparam(args);
                    result = sqlsession.selectone(command.getname(), param);
                    if (method.returnsoptional() && 
                        (result == null || 
                         !method.getreturntype().equals(result.getclass()))) {
                        result = optional.ofnullable(result);
                    }
                }
                break;
            case flush:
                result = sqlsession.flushstatements();
                break;
            default:
                throw new bindingexception(
                    "unknown execution method for: " + command.getname()
                );
        }
        if (result == null && method.getreturntype().isprimitive() && 
            !method.returnsvoid()) {
            throw new bindingexception(
                "mapper method '" + command.getname() + 
                "' attempted to return null from a method with " +
                "a primitive return type (" + method.getreturntype() + ")."
            );
        }
        return result;
    }
    // 执行返回多条的查询
    private <e> object executeformany(sqlsession sqlsession, object[] args) {
        list<e> result;
        object param = method.convertargstosqlcommandparam(args);
        if (method.hasrowbounds()) {
            rowbounds rowbounds = method.extractrowbounds(args);
            result = sqlsession.selectlist(
                command.getname(), param, rowbounds
            );
        } else {
            result = sqlsession.selectlist(command.getname(), param);
        }
        return result;
    }
    // 执行返回map的查询
    private <k, v> map<k, v> executeformap(sqlsession sqlsession, 
                                            object[] args) {
        object param = method.convertargstosqlcommandparam(args);
        map<k, v> result;
        if (method.hasrowbounds()) {
            rowbounds rowbounds = method.extractrowbounds(args);
            result = sqlsession.selectmap(
                command.getname(), param, method.getmapkey(), rowbounds
            );
        } else {
            result = sqlsession.selectmap(
                command.getname(), param, method.getmapkey()
            );
        }
        return result;
    }
    // 处理行数结果
    private object rowcountresult(int rowcount) {
        final object result;
        if (method.returnsvoid()) {
            result = null;
        } else if (integer.type.equals(method.getreturntype()) || 
                   integer.class.equals(method.getreturntype())) {
            result = rowcount;
        } else if (long.type.equals(method.getreturntype()) || 
                   long.class.equals(method.getreturntype())) {
            result = (long) rowcount;
        } else if (boolean.type.equals(method.getreturntype()) || 
                   boolean.class.equals(method.getreturntype())) {
            result = rowcount > 0;
        } else {
            throw new bindingexception(
                "mapper method '" + command.getname() + 
                "' has an unsupported return type: " + 
                method.getreturntype()
            );
        }
        return result;
    }
    //内部类:sqlcommand
    public static class sqlcommand {
        private final string name;
        private final sqlcommandtype type;
        public sqlcommand(configuration configuration, 
                         class<?> mapperinterface, method method) {
            final string methodname = method.getname();
            final class<?> declaringclass = method.getdeclaringclass();
            // 解析mappedstatement
            mappedstatement ms = resolvemappedstatement(
                mapperinterface, methodname, declaringclass, configuration
            );
            if (ms == null) {
                if (method.getannotation(flush.class) != null) {
                    name = null;
                    type = sqlcommandtype.flush;
                } else {
                    throw new bindingexception(
                        "invalid bound statement (not found): " + 
                        mapperinterface.getname() + "." + methodname
                    );
                }
            } else {
                name = ms.getid();
                type = ms.getsqlcommandtype();
                if (type == sqlcommandtype.unknown) {
                    throw new bindingexception(
                        "unknown execution method for: " + name
                    );
                }
            }
        }
        private mappedstatement resolvemappedstatement(
            class<?> mapperinterface, string methodname, 
            class<?> declaringclass, configuration configuration) {
            string statementid = mapperinterface.getname() + "." + methodname;
            if (configuration.hasstatement(statementid)) {
                return configuration.getmappedstatement(statementid);
            }
            return null;
        }
        public string getname() {
            return name;
        }
        public sqlcommandtype gettype() {
            return type;
        }
    }
    //内部类:methodsignature
    public static class methodsignature {
        private final boolean returnsmany;
        private final boolean returnsmap;
        private final boolean returnsvoid;
        private final boolean returnscursor;
        private final boolean returnsoptional;
        private final class<?> returntype;
        private final string mapkey;
        private final integer resulthandlerindex;
        private final integer rowboundsindex;
        private final paramnameresolver paramnameresolver;
        public methodsignature(configuration configuration, 
                              class<?> mapperinterface, method method) {
            type resolvedreturntype = typeparameterresolver(method);
            // 解析返回类型
            if (resolvedreturntype instanceof void) {
                this.returnsvoid = true;
            } else if (collection.class.isassignablefrom(
                (class<?>) resolvedreturntype) || 
                resolvedreturntype.isarray()) {
                this.returnsmany = true;
            } else if (map.class.isassignablefrom(
                (class<?>) resolvedreturntype)) {
                this.returnsmap = true;
            } else {
                this.returnsmany = false;
                this.returnsmap = false;
            }
            // ... 省略其他初始化代码
        }
        public object convertargstosqlcommandparam(object[] args) {
            return paramnameresolver.getnamedparams(args);
        }
        public boolean hasrowbounds() {
            return rowboundsindex != null;
        }
    }
}

4.4 代理执行流程

1. 调用mapper接口方法
   ↓
2. 触发mapperproxy.invoke()
   ↓
3. 获取或创建mappermethod
   ↓
4. 执行mappermethod.execute()
   ↓
5. 根据sql类型调用sqlsession方法
   ↓
6. executor执行sql
   ↓
7. 返回结果

五、mapper注册流程

mapper接口需要注册到mybatis才能使用。

5.1 注册方式

<configuration>
    <!-- 注册mapper接口 -->
    <mappers>
        <!--使用类路径 -->
        <mapper class="com.example.mapper.usermapper"/>
        <!--使用包扫描 -->
        <package name="com.example.mapper"/>
        <!--使用xml资源路径 -->
        <mapper resource="com/example/mapper/usermapper.xml"/>
    </mappers>
</configuration>
// 创建configuration
configuration configuration = new configuration();
// 注册mapper接口
configuration.addmapper(usermapper.class);
configuration.addmapper(ordermapper.class);
// 或者通过mapperregistry
mapperregistry mapperregistry = configuration.getmapperregistry();
mapperregistry.addmapper(usermapper.class);
sqlsessionfactorybuilder builder = new sqlsessionfactorybuilder();
// 通过inputstream
sqlsessionfactory factory = builder.build(inputstream);
// 通过configuration
environment environment = new environment("development", ...);
configuration configuration = new configuration(environment);
configuration.addmapper(usermapper.class);
sqlsessionfactory factory = 
    new sqlsessionfactorybuilder().build(configuration);
public class configuration {
    protected final mapperregistry mapperregistry = 
        new mapperregistry(this);
    // 添加mapper
    public <t> void addmapper(class<t> type) {
        mapperregistry.addmapper(type);
    }
    // 获取mapper
    public <t> t getmapper(class<t> type, sqlsession sqlsession) {
        return mapperregistry.getmapper(type, sqlsession);
    }
    // 检查mapper是否存在
    public boolean hasmapper(class<?> type) {
        return mapperregistry.hasmapper(type);
    }
    public mapperregistry getmapperregistry() {
        return mapperregistry;
    }
}
public class mapperscannerconfigurer {
    // 扫描包路径
    private string basepackage;
    public void postprocessbeandefinitionregistry(
        beandefinitionregistry registry) {
        // 创建扫描器
        classpathmapperscanner scanner = 
            new classpathmapperscanner(registry);
        // 设置扫描包
        scanner.scan(stringutils.tokenizetostringarray(
            this.basepackage,
            configurableapplicationcontext.config_location_delimiters
        ));
    }
}
class classpathmapperscanner extends classpathbeandefinitionscanner {
    @override
    public set<beandefinitionholder> doscan(string... basepackages) {
        set<beandefinitionholder> beandefinitions = 
            super.doscan(basepackages);
        if (beandefinitions.isempty()) {
            logger.warn("no mybatis mapper was found in '" + 
                arrays.tostring(basepackages) + 
                "' package. please check your configuration.");
        } else {
            // 处理beandefinition
            processbeandefinitions(beandefinitions);
        }
        return beandefinitions;
    }
    private void processbeandefinitions(
        set<beandefinitionholder> beandefinitions) {
        genericbeandefinition definition;
        for (beandefinitionholder holder : beandefinitions) {
            definition = (genericbeandefinition) holder.getbeandefinition();
            // 设置beanclass为mapperfactorybean
            definition.getconstructorargumentvalues()
                .addgenericargumentvalue(definition.getbeanclassname());
            definition.setbeanclass(this.mapperfactorybean.getclass());
            // ... 省略其他配置
        }
    }
}

六、mapper执行流程

理解mapper的执行流程对于调试和优化至关重要。

6.1 完整执行流程

//1.获取sqlsession
sqlsession session = sqlsessionfactory.opensession();
try {
    //2.获取mapper代理对象
    usermapper usermapper = session.getmapper(usermapper.class);
    //3.调用mapper方法
    user user = usermapper.selectbyid(1l);
    //4.使用结果
    system.out.println(user);
    //5.提交事务
    session.commit();
} finally {
    //6.关闭session
    session.close();
}

6.2 详细执行步骤

步骤1: 获取mapper代理对象

session.getmapper(usermapper.class)
↓
configuration.getmapper(usermapper.class, this)
↓
mapperregistry.getmapper(usermapper.class, this)
↓
mapperproxyfactory.newinstance(this)
↓
proxy.newproxyinstance(...) → 创建代理对象

步骤2: 调用mapper方法

   usermapper.selectbyid(1l)
    ↓
    mapperproxy.invoke(proxy, method, args)
    ↓
    cachedmappermethod(method)
    ↓
    mappermethod.execute(sqlsession, args)

步骤3: 执行sql

sqlcommandtype.select
↓
sqlsession.selectone(statementid, param)
↓
executor.query(ms, param, rowbounds, resulthandler)
↓
创建cachekey
↓
查询缓存
↓
查询数据库
↓
映射结果

步骤4: 返回结果

 处理resultset
    ↓
    objectfactory创建对象
    ↓
    resulthandler映射
    ↓
    返回user对象

6.3 执行时序图

应用 → mapperproxy → mappermethod → sqlsession → 
executor → statementhandler → database

6.4 异常处理

try {
    user user = usermapper.selectbyid(1l);
} catch (persistenceexception e) {
    // 持久化异常
    throwable cause = e.getcause();
    if (cause instanceof sqlexception) {
        // 处理sql异常
        sqlexception sqlex = (sqlexception) cause;
        system.out.println("sql error: " + sqlex.getmessage());
    }
} catch (bindingexception e) {
    // 绑定异常:mapper方法未找到
    system.out.println("mapper method not found: " + e.getmessage());
}

七、最佳实践

7.1 mapper设计建议

单一职责 - 每个mapper对应一张表
命名规范 - 接口名与实体名对应
方法命名 - 使用语义化的方法名
参数设计 - 使用@param注解明确参数名

7.2 性能优化建议

合理使用缓存 - 二级缓存提升性能
避免n+1查询 - 使用关联查询
分页查询 - 使用rowbounds或pagehelper
批量操作 - 使用batchexecutor

7.3 常见问题解决

//错误:多参数未使用@param
list<user> select(string name, integer age);
//正确:使用@param
list<user> select(@param("name") string name,
                 @param("age") integer age);
<!--正确:使用resultmap -->
<resultmap id="baseresultmap" type="user">
    <id column="id" property="id"/>
    <result column="user_name" property="name"/>
</resultmap>
<select id="selectbyid" resultmap="baseresultmap">
    select id, user_name from t_user where id = #{id}
</select>

八、总结

mybatis的映射器模块通过接口+配置的方式,实现了优雅的数据库操作。
mapper接口 - 定义数据库操作方法
sql映射 - xml或注解配置sql语句
动态代理 - jdk动态代理自动实现接口
mapperproxy - 拦截方法调用并执行sql
mappermethod - 封装方法执行逻辑

到此这篇关于mybatis映射器模块最佳实践的文章就介绍到这了,更多相关mybatis映射器模块内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com