展开菜单
首页 精品内容 本月促销 装机必备 Windows PDF教程 macOS软件 IOS软件 Android AI 专题
全部分类

当前位置:

首页 > 编程开发 > 怎么用Java手写持久层框架

怎么用Java手写持久层框架

JDBC操作回顾及问题分析学习java的同学一定避免不了接触过jdbc,让我们来回顾下初学时期接触的jdbc操作吧以下代码连接数据库查询用户表信息,用户表字段分别为用户id,用户名username。Connectionconnection=null;PreparedStatementpreparedStatement=null;ResultSetresultSet=null;Useruser=newUser();try{//加载数据库驱动//Class.forName("com.mysql.jdbc.Dr

本文目录

    JDBC操作回顾及问题分析

    学习java的同学一定避免不了接触过jdbc,让我们来回顾下初学时期接触的jdbc操作吧

    以下代码连接数据库查询用户表信息,用户表字段分别为用户id,用户名username。

    Connection connection = null;
            PreparedStatement preparedStatement = null;
            ResultSet resultSet = null;
            User user = new User();
            try {
                // 加载数据库驱动
                //Class.forName("com.mysql.jdbc.Driver");
                Class.forName("com.mysql.cj.jdbc.Driver");
                // 通过驱动管理类获取数据库链接
                connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "mimashi3124");
                // 定义sql语句?表示占位符
                String sql = "select * from user where username = ?";
                // 获取预处理statement
                preparedStatement = connection.prepareStatement(sql);
                // 设置参数,第⼀个参数为sql语句中参数的序号(从1开始),第⼆个参数为设置的参数值
                preparedStatement.setString(1, "盖伦");
                // 向数据库发出sql执⾏查询,查询出结果集
                resultSet = preparedStatement.executeQuery();
                // 遍历查询结果集
                while (resultSet.next()) {
                    int id = resultSet.getInt("id");
                    String username = resultSet.getString("username");
                    // 封装User
                    user.setId(id);
                    user.setUsername(username);
                }
                System.out.println(user);
            } catch (
                    Exception e) {
                e.printStackTrace();
            } finally {
                // 释放资源
                if (resultSet != null) {
                    try {
                        resultSet.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (preparedStatement != null) {
                    try {
                        preparedStatement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (connection != null) {
                    try {
                        connection.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
            }

    查看代码我们可以发现使用JDBC操作数据库存在以下问题:

    • 数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能。

    • Sql语句我们是写在代码里的,代码不容易维护,实际应⽤中sql变化的可能较⼤,sql变动需要改变 java代码。

    • 使⽤preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不⼀定,可能多也可能少,修改sql还要修改代码,系统不易维护。

    • 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成pojo对象解析⽐较⽅便

    问题解决思路

    • 使⽤数据库连接池初始化连接资源,避免资源浪费

    • 将sql语句抽取到xml配置中,这种sql的变动只用关注xml文件,不比去一堆java代码里改写sql

    • 参数硬编码问题可以使用反射、内省等技术、自动将实体与表字段进行映射。

    自己动手写个持久层框架

    接下来,我们来一个个解决上面的问题

    数据库连接池我们可以直接使用c3p0提供的ComboPooledDataSource即可

    为了解决sql硬编码问题,我们要把sql写到xml文件中,那自然是要定义一个xml文件了。

    光有sql肯定不行,毕竟我们要先连接数据库,sql语句才有存在的意义。所以xml中得先定义数据配置信息,然后才是sql语句。

    1.定义配置xml文件

    我们新建一个sqlMapConfig.xml,定义数据源信息、并且增加两个sql语句,parameterType为sql执行参数,resultType为方法返回实体。

    代码如下(数据库不同版本使用驱动类可能不同):

    
        
        
    
        
        
        
    
    	
            select * from user where id = #{id} and username =#{username}
        
    
        
            select * from user
        
    

    现在xml文件数据库信息也有了,sql语句定义也有了,还有什么问题呢?

    我们实际中对sql的操作会涉及到不同的表,所以我们改进一下,把每个表的sql语句单独放在一个xml里,这样结构更清晰就容易维护。

    优化以后的xml配置现在是这样了

    sqlMapConfig.xml

    
        
        
    	
        
        
        
        
    	
    

    mapper.xml

    
        
            select * from user where id = #{id} and username =#{username}
        
    
        
            select * from user
        
    

    顺便定义一下业务实体User

    public class User {
        private int id;
        private String username;
        
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", username='" + username + '\'' +
                    '}';
        }
    }

    2.读取配置文件

    读取完成以后以流的形式存在,不好操作,所以我们要做解析拿到信息,创建实体对象来存储。

    Configuration : 存放数据库基本信息、Map<唯⼀标识,Mapper> 唯⼀标识:namespace + "." + idMappedStatement:存放sql语句、输⼊参数类型、输出参数类型

    xml解析我们使用dom4j

    首先引入maven依赖

    代码如下(mysql驱动版本根据实际使用mysql版本调整):

    
            8
            8
        
    
        
            
                mysql
                mysql-connector-java
                8.0.22
            
            
                c3p0
                c3p0
                0.9.1.2
            
            
                log4j
                log4j
                1.2.12
            
            
                junit
                junit
                4.10
            
            
                dom4j
                dom4j
                1.6.1
            
            
                jaxen
                jaxen
                1.1.6
            
        

    数据库配置实体 Configuration

    public class Configuration {
        //数据源
        private DataSource dataSource;
        //map集合: key:statementId value:MappedStatement
        private Map mappedStatementMap = new HashMap<>();
    
        public DataSource getDataSource() {
            return dataSource;
        }
    
        public Configuration setDataSource(DataSource dataSource) {
            this.dataSource = dataSource;
            return this;
        }
    
        public Map getMappedStatementMap() {
            return mappedStatementMap;
        }
    
        public Configuration setMappedStatementMap(Map mappedStatementMap) {
            this.mappedStatementMap = mappedStatementMap;
            return this;
        }
    }

    Sql语句信息实体

    public class MappedStatement {
    
        //id
        private String id;
        //sql语句
        private String sql;
        //输⼊参数
        private String parameterType;
        //输出参数
        private String resultType;
    
        public String getId() {
            return id;
        }
    
        public MappedStatement setId(String id) {
            this.id = id;
            return this;
        }
    
        public String getSql() {
            return sql;
        }
    
        public MappedStatement setSql(String sql) {
            this.sql = sql;
            return this;
        }
    
        public String getParameterType() {
            return parameterType;
        }
    
        public MappedStatement setParameterType(String parameterType) {
            this.parameterType = parameterType;
            return this;
        }
    
        public String getResultType() {
            return resultType;
        }
    
        public MappedStatement setResultType(String resultType) {
            this.resultType = resultType;
            return this;
        }
    }

    顺便定义一个Resources类来读取xml文件流

    public class Resources {
        public static InputStream getResourceAsSteam(String path) {
            return Resources.class.getClassLoader().getResourceAsStream(path);
        }
    }

    接下来就是实际的解析了,因为解析代码比较多,我们考虑封装类单独处理解析

    定义XMLConfigBuilder类解析数据库配置信息

    public class XMLConfigBuilder {
    
        private Configuration configuration;
    
    
        public XMLConfigBuilder() {
            this.configuration = new Configuration();
        }
    
        public Configuration parserConfiguration(InputStream inputStream) throws DocumentException, PropertyVetoException, ClassNotFoundException {
            Document document = new SAXReader().read(inputStream);
            Element rootElement = document.getRootElement();
            List propertyElements = rootElement.selectNodes("//property");
            Properties properties = new Properties();
            for (Element propertyElement : propertyElements) {
                String name = propertyElement.attributeValue("name");
                String value = propertyElement.attributeValue("value");
                properties.setProperty(name,value);
            }
            //解析到数据库配置信息,设置数据源信息
            ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
            comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
            comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
            comboPooledDataSource.setUser(properties.getProperty("username"));
            comboPooledDataSource.setPassword(properties.getProperty("password"));
    		
            configuration.setDataSource(comboPooledDataSource);
    
    		//将configuration传入XMLMapperBuilder中做sql语句解析。
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(this.configuration);
            List mapperElements = rootElement.selectNodes("//mapper");
            for (Element mapperElement : mapperElements) {
                String mapperPath = mapperElement.attributeValue("resource");
                InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(mapperPath);
                xmlMapperBuilder.parse(resourceAsStream);
            }
            return configuration;
        }
    }

    定义XMLMapperBuilder类解析数据库配置信息

    public class XMLMapperBuilder {
    
        private Configuration configuration;
    
        public XMLMapperBuilder(Configuration configuration) {
            this.configuration = configuration;
        }
        public void parse(InputStream inputStream) throws DocumentException,
                ClassNotFoundException {
            Document document = new SAXReader().read(inputStream);
            Element rootElement = document.getRootElement();
            String namespace = rootElement.attributeValue("namespace");
            List select = rootElement.selectNodes("select");
            for (Element element : select) { //id的值
                String id = element.attributeValue("id");
                String parameterType = element.attributeValue("parameterType"); //输⼊参数
                String resultType = element.attributeValue("resultType"); //返回参数
                //statementId,后续调用通过statementId,找到对应的sql执行
                String key = namespace + "." + id;
                //sql语句
                String textTrim = element.getTextTrim();
                //封装 mappedStatement
                MappedStatement mappedStatement = new MappedStatement();
                mappedStatement.setId(id);
                mappedStatement.setParameterType(parameterType);
                mappedStatement.setResultType(resultType);
                mappedStatement.setSql(textTrim);
                //填充 configuration
                configuration.getMappedStatementMap().put(key, mappedStatement);
            }
        }
    }

    现在我们可以通过调用配置解析的方法拿到Configuration对象了。但是我们实际使用,肯定是希望我给你配置信息、sql语句,再调用你的方法就返回结果了。
    所以我们还需要定义一个数据库操作接口(类)

    3.定义sql操作接口SqlSession

    public interface SqlSession {
    	//查询多个
        public  List selectList(String statementId, Object... param) throws Exception;
    
    	//查询一个
        public  T selectOne(String statementId,Object... params) throws Exception;
    }

    对操作接口SqlSession做具体实现,这里主要是通过statementId找到对应的sql信息,进行执行

    代码中simpleExcutor做真正的数据库语句执行、返回参数封装等操作

    public class DefaultSqlSession implements SqlSession {
    
        private Configuration configuration;
        private Executor simpleExcutor = new SimpleExecutor();
    
        public DefaultSqlSession(Configuration configuration) {
            this.configuration = configuration;
        }
    
        @Override
        public  List selectList(String statementId, Object... param) throws Exception {
            MappedStatement mappedStatement =
                    configuration.getMappedStatementMap().get(statementId);
            List query = simpleExcutor.query(configuration, mappedStatement, param);
            return query;
        }
    
        @Override
        public  T selectOne(String statementId, Object... params) throws Exception {
            List objects = selectList(statementId, params);
            if (objects.size() == 1) {
                return (T) objects.get(0);
            } else {
                throw new RuntimeException("返回结果过多");
            }
        }
    }

    4.编写数据库执行逻辑

    数据库操作类DefaultSqlSession中的selectList方法调用到了simpleExcutor.query()方法

    public class SimpleExecutor implements Executor {
    
        private Connection connection = null;
    
        @Override
        public  List query(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws Exception {
            //获取连接
            connection = configuration.getDataSource().getConnection();
            // select * from user where id = #{id} and username = #{username} String sql =
            String sql = mappedStatement.getSql();
            //对sql进⾏处理
            BoundSql boundSql = getBoundSql(sql);
            // 3.获取预处理对象:preparedStatement
            PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
    
            // 4. 设置参数
            //获取到了参数的全路径
            String parameterType = mappedStatement.getParameterType();
            Class parameterTypeClass = getClassType(parameterType);
    
            List parameterMappingList = boundSql.getParameterMappingList();
            for (int i = 0; i < parameterMappingList.size(); i++) {
                ParameterMapping parameterMapping = parameterMappingList.get(i);
                String content = parameterMapping.getContent();
    
                //反射
                Field declaredField = parameterTypeClass.getDeclaredField(content);
                //暴力访问
                declaredField.setAccessible(true);
                Object o = declaredField.get(params[0]);
    
                preparedStatement.setObject(i+1,o);
    
            }
            // 5. 执行sql
            ResultSet resultSet = preparedStatement.executeQuery();
            String resultType = mappedStatement.getResultType();
            Class resultTypeClass = getClassType(resultType);
    
            ArrayList objects = new ArrayList<>();
    
            // 6. 封装返回结果集
            while (resultSet.next()){
                Object o =resultTypeClass.newInstance();
                //元数据
                ResultSetMetaData metaData = resultSet.getMetaData();
                for (int i = 1; i <= metaData.getColumnCount(); i++) {
                    // 字段名
                    String columnName = metaData.getColumnName(i);
                    // 字段的值
                    Object value = resultSet.getObject(columnName);
                    //使用反射或者内省,根据数据库表和实体的对应关系,完成封装
                    PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    writeMethod.invoke(o,value);
                }
                objects.add(o);
            }
            return (List) objects;
        }
    
        @Override
        public void close() throws SQLException {
    
        }
    
        private Class getClassType(String parameterType) throws ClassNotFoundException {
            if(parameterType!=null){
                Class aClass = Class.forName(parameterType);
                return aClass;
            }
            return null;
    
        }
    
        private BoundSql getBoundSql(String sql) {
            //标记处理类:主要是配合通⽤标记解析器GenericTokenParser类完成对配置⽂件等的解 析⼯作,其中
            //TokenHandler主要完成处理
            ParameterMappingTokenHandler parameterMappingTokenHandler = new
                    ParameterMappingTokenHandler();
            //GenericTokenParser :通⽤的标记解析器,完成了代码⽚段中的占位符的解析,然后再根 据给定的
           // 标记处理器(TokenHandler)来进⾏表达式的处理
            //三个参数:分别为openToken (开始标记)、closeToken (结束标记)、handler (标记处 理器)
            GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}",
                    parameterMappingTokenHandler);
            String parse = genericTokenParser.parse(sql);
            List parameterMappings =
                    parameterMappingTokenHandler.getParameterMappings();
            return new BoundSql(parse, parameterMappings);
        }
    }

    上面的注释比较详细,流程为

    • 根据对应的statementId获取到要执行的sql语句、调用参数、返回参数。

    • 对sql的占位符进行解析、调用参数进行设置

    • 根据解析到的入参字段,通过反射获取到对应的值,进行sql语句参数设定

    • 执行sql语句,使用反射、内省,根据数据库表和实体的对应关系,完成对象属性的设置,最终返回结果。

    通过以上步骤,我们获取到了数据库配置、sql语句信息。定义了数据库操作类SqlSession,但是我们并没有在什么地方调用解析配置文件。

    我们还需要一个东西把两者给串起来,这里我们可以使用工厂模式来生成SqlSession

    使用工厂模式创建SqlSession

    public interface SqlSessionFactory {
        public SqlSession openSession();
    }
    public class DefaultSqlSessionFactory implements SqlSessionFactory{
    
        private Configuration configuration;
    
        public DefaultSqlSessionFactory(Configuration configuration) {
            this.configuration = configuration;
        }
    
        @Override
        public SqlSession openSession() {
            return new DefaultSqlSession(configuration);
        }
    }

    同时为了屏蔽构建SqlSessionFactory工厂类时获取Configuration的解析过程,我们可以使用构建者模式来获得一个SqlSessionFactory类。

    public class SqlSessionFactoryBuilder {
        public SqlSessionFactory build(InputStream inputStream) throws PropertyVetoException, DocumentException, ClassNotFoundException {
            XMLConfigBuilder xmlConfigerBuilder = new XMLConfigBuilder();
            Configuration configuration = xmlConfigerBuilder.parserConfiguration(inputStream);
            SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
            return sqlSessionFactory;
        }
    }

    5.调用测试

    终于好了,通过以上几个步骤我们现在可以具体调用执行代码了。

     public static void main(String[] args) throws Exception {
            InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            User user = new User();
            user.setId(1);
            user.setUsername("盖伦");
            User user2 = sqlSession.selectOne("user.selectOne", user);
    
            System.out.println(user2);
            List users = sqlSession.selectList("user.selectList");
            for (User user1 : users) {
                System.out.println(user1);
            }
        }

    代码正确执行,输出

    怎么用Java手写持久层框架

    ⾃定义框架优化

    上述⾃定义框架,解决了JDBC操作数据库带来的⼀些问题:例如频繁创建释放数据库连接,硬编
    码,⼿动封装返回结果集等问题,现在我们继续来分析刚刚完成的⾃定义框架代码,有没有什么问题呢?

    问题如下:

    • dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调⽤sqlsession⽅ 法,关闭sqlsession)

    • dao的实现类中存在硬编码,调⽤sqlsession的⽅法时,参数statement的id硬编码

    我们可以使用代理模式,生成代理对象,在调用之前获取到执行方法的方法名、具体类。这样我们就能获取到statementId。

    为SqlSession类新增getMappper方法,获取代理对象

    public interface SqlSession {
        public  List selectList(String statementId, Object... param) throws Exception;
    
        public  T selectOne(String statementId,Object... params) throws Exception;
    
        //为Dao接口生成代理实现类
        public  T getMapper(Class mapperClass);
    }
    public class DefaultSqlSession implements SqlSession {
    
        private Configuration configuration;
        private Executor simpleExcutor = new SimpleExecutor();
    
        public DefaultSqlSession(Configuration configuration) {
            this.configuration = configuration;
        }
    
        @Override
        public  List selectList(String statementId, Object... param) throws Exception {
            MappedStatement mappedStatement =
                    configuration.getMappedStatementMap().get(statementId);
            List query = simpleExcutor.query(configuration, mappedStatement, param);
            return query;
        }
    
        @Override
        public  T selectOne(String statementId, Object... params) throws Exception {
            List objects = selectList(statementId, params);
            if (objects.size() == 1) {
                return (T) objects.get(0);
            } else {
                throw new RuntimeException("返回结果过多");
            }
        }
    
        @Override
        public  T getMapper(Class mapperClass) {
            Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                	// selectOne
                    String methodName = method.getName();
                    // className:namespace
                    String className = method.getDeclaringClass().getName();
                    //statementId
                    String statementId = className+'.'+methodName;
                    Type genericReturnType = method.getGenericReturnType();
                    //判断是否实现泛型类型参数化
                    if (genericReturnType instanceof ParameterizedType){
                        List objects = selectList(statementId,args);
                        return objects;
                    }
                    return selectOne(statementId,args);
                }
            });
            return (T) proxyInstance;
        }
    }

    定义业务数据dao接口

    public interface IUserDao {
        //查询所有用户
        public List findAll() throws Exception;
        //根据条件进行用户查询
        public User findByCondition(User user) throws Exception;
    }

    接下来我们只需获取到代理对象,调用方法即可。

    public class Main2 {
        public static void main(String[] args) throws Exception {
            InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
            SqlSession sqlSession = sqlSessionFactory.openSession();
    		//获取到代理对象
            IUserDao userDao = sqlSession.getMapper(IUserDao.class);
            List all = userDao.findAll();
            for (User user1 : all) {
                System.out.println(user1);
            }
        }
    }

    怎么用Java手写持久层框架

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Java
    相关文章 更多
    精品专题 更多
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    IOS软件

    正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

    AI

    正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

    Mac软件 更多
    Aperty macOS版
    Aperty macOS版

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    火山引擎 网页版
    火山引擎 网页版

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    立刻MV 网页版
    立刻MV 网页版

    立刻MV 面向音乐人、内容创作者和品牌团队,帮助用户低成本完成歌曲 MV、歌词视频、AI 分镜、图生视频与最终合成。

    蛙蛙写作 网页版
    蛙蛙写作 网页版

    蛙蛙写作 AI 创作工具,覆盖小说、剧本、文案,一键生成文稿,打通文字转视频全流程,高效解决创作难题。

    讯飞智作 网页版
    讯飞智作 网页版

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    超级简历WonderCV 网页版
    超级简历WonderCV 网页版

    超级简历WonderCV是面向应届生和AI小白的智能求职平台,覆盖简历、面试与职业规划。

    音述AI 网页版
    音述AI 网页版

    音述AI,是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区。

    AionClaw macOS版
    AionClaw macOS版

    AionClaw 是可在桌面端运行的任务型 AI 助手,通过数字专家与技能市场组合办公、创作、数据和开发能力。

    Loomy macOS版
    Loomy macOS版

    Loomy 是科大讯飞推出的桌面 AI 助手,可通过对话、任务与工作目录协助处理办公、内容创作和资料整理。

    AirBuddy 3 macOS版
    AirBuddy 3 macOS版

    AirBuddy 3 是面向 macOS 的蓝牙外设增强工具,可集中查看 AirPods、Beats 与苹果设备电量,提供快速连接、播放控制、电量提醒、桌面小组件和多台 Mac 外设接力等功能。

    AirServer macOS版
    AirServer macOS版

    AirServer macOS版可把 Mac 转换为无线投屏接收端,通过 AirPlay 或 Google Cast 接收手机、平板和电脑画面,并支持多设备同时连接、高清镜像及会话录制,适合家庭娱乐、课堂教学、会议演示和应用展示。

    AnyDesk macOS版
    AnyDesk macOS版

    AnyDesk macOS版是一款适用于远程办公、设备维护和客户支持的远程桌面工具,可连接并操作异地计算机,支持文件传输、无人值守访问、会话权限控制及跨平台协作。

    WINDOWS 更多
    讯飞绘文 网页版
    讯飞绘文 网页版

    讯飞绘文网页版是科大讯飞推出的AI图文创作与内容运营工具,覆盖热点选题、文章生成、稿件校对、智能配图、排版发布和数据复盘等环节,适合自媒体创作者及内容团队在线使用。

    Maya(Autodesk Maya) Windows版
    Maya(Autodesk Maya) Windows版

    Autodesk Maya 是面向影视、电视、动画和游戏行业的专业三维制作软件,集多边形与 NURBS 建模、角色绑定、关键帧和动作捕捉动画、Bifrost 特效模拟、材质开发及 Arnold 渲染于一体,

    Aperty Windows版
    Aperty Windows版

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    火山引擎 网页版
    火山引擎 网页版

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    立刻MV 网页版
    立刻MV 网页版

    立刻MV 面向音乐人、内容创作者和品牌团队,帮助用户低成本完成歌曲 MV、歌词视频、AI 分镜、图生视频与最终合成。

    蛙蛙写作 网页版
    蛙蛙写作 网页版

    蛙蛙写作 AI 创作工具,覆盖小说、剧本、文案,一键生成文稿,打通文字转视频全流程,高效解决创作难题。

    讯飞智作 网页版
    讯飞智作 网页版

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    超级简历WonderCV 网页版
    超级简历WonderCV 网页版

    超级简历WonderCV是面向应届生和AI小白的智能求职平台,覆盖简历、面试与职业规划。

    音述AI 网页版
    音述AI 网页版

    音述AI,是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区。

    AionClaw Windows版
    AionClaw Windows版

    AionClaw 是可在桌面端运行的任务型 AI 助手,通过数字专家与技能市场组合办公、创作、数据和开发能力。

    Loomy Windows版
    Loomy Windows版

    Loomy 是科大讯飞推出的桌面 AI 助手,可通过对话、任务与工作目录协助处理办公、内容创作和资料整理。

    AirServer Windows版
    AirServer Windows版

    AirServer Windows版可把电脑变成AirPlay、Google Cast和Miracast无线投屏接收端,适合会议演示、课堂教学及家庭大屏分享。发送设备通常无需另装客户端,并可同时接收和智能排列多个镜像会话。

    网站备案号:苏ICP备2026018738号-1 联系邮箱:bd@zhengruan.com

    Copyright ©2018-2026