本节内容主要就几个常用的模块进行解读,在众多企业级开发中常常用得到
例如邮件发送这一功能其实非常常见,在一般的网站设计中会有对注册邮件的激活功能,此时就需要用到这个功能了,一般来说激活邮件需要设置UUID 来进行验证,如果用户点击了链接那么在数据库的状态就转换为1,表示已经激活了。
定时任务也用得比较多,常见于缓存进入数据库等,可以看我这篇关于高并发的章节 高并发Demo
这里讲的批处理主要是针对批量导入数据库的快捷方法(这样就不用进入excel一行一行导入了)
目前很多项目也是需要讲究前后端分离的,所以一个好的接口测试是非常有必要的,之前我们使用了Postman 来进行测试,不过依旧缺少点详细信息,这里使用Swagger2 来进行测试,不过值得一提的是,以前老的配置方法非常坑,我也是花了相当的时间才找到了一个合适的办法。

希望对你有所帮助!

目录

  • 邮件发送
    • 发送前的准备
    • 发送
  • 定时任务
    • @Scheduled
    • Quartz
  • 批处理
  • Swagger 2

邮件发送

邮件发送是非常常见的功能,注册时的身份验证、重要通知发送都会用到邮件发送。Sun公司提供了JavaMail,但是配置相当繁琐,Spring 提供了JavaMailSender来简化,Spring Boot则提供了MailSenderAutoConfiguration对邮件做了进一步简化。

发送前的准备

这里以QQ邮箱发送邮件为例:需要开通POP3/SMTP服务或者IMAP/SMTP服务。SMTP就是简单邮件传输协议,定义了邮件客户端和SMTP服务器之间、以及SMTP与SMTP之间的通信规则。也就是说aaa@qq.com 用户先将邮件投递给腾讯的SMTP,然后邮件投递到网易的SMTP服务器,此时SMTP服务器就是用来接收邮件的。POP3 邮局协议,定义了客户端与POP3 服务器之间的规则。

这样你就会得到一个授权码了。

发送

添加邮件依赖:

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId></dependency>

项目创建成功后,在application.properties 完成邮件基本信息配置:

spring.mail.host=smtp.qq.com
spring.mail.port=465
spring.mail.username = 1739994163@qq.com
spring.mail.password=授权码
spring.mail.default-encoding=utf-8
spring.mail.properties.mail.smtp.socketFactory.class = javax.net.ssl.SSLSocketFactory
spring.mail.properties.mail.debug=true

这里配置了邮件服务器的地址、端口(可以是465或者587)、用户账号和密码以及默认编码、SSL连接配置,最后开启debug,这样方便开发者查看邮件发送日志。注意,SSL的配置可以在QQ邮箱官方文档查看

发送一个简单的邮件,创建一个MailService用来封装邮件的发送:

@Component
public class MailService {@Resource
// 在MailSenderPropertiesConfiguration类配置好的,该类在Mail 自动配置类导入,
// 只要注入就可以了JavaMailSender javaMailSender;public void sendSimpleMail(String from, String to, String cc, String subject, String content) {// 5个参数,分别是邮件发送者,收件人,抄送人,邮件主题,邮件内容      SimpleMailMessage simpMsg = new SimpleMailMessage();simpMsg.setFrom(from);simpMsg.setTo(to);simpMsg.setCc(cc);simpMsg.setSubject(subject);simpMsg.setText(content);javaMailSender.send(simpMsg);}
}

配置完成后,可以在单元测试中写一个测试方法进行测试:

import com.example.testspringboot.service.MailService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;import javax.annotation.Resource;
import java.io.File;@RunWith(SpringRunner.class)
@SpringBootTestpublic class SendmailApplicationTests {@ResourceMailService mailService;@Testpublic void sendSimpleMail() {mailService.sendSimpleMail("1739994163@qq.com","qazjou@163.com","1739994163@qq.com","测试邮件主题","测试邮件内容");}
}

执行该方法,便可以看到邮件发送成功了:

发送带附件的邮件:
通过调用Attachment 方法即可添加附件,该方法调用多个方法添加附件,在MailService添加:

 public void sendAttachFileMail(String from, String to,String subject, String content, File file) {try {MimeMessage message = javaMailSender.createMimeMessage();MimeMessageHelper helper = new MimeMessageHelper(message,true);helper.setFrom(from);helper.setTo(to);helper.setSubject(subject);helper.setText(content);helper.addAttachment(file.getName(), file);javaMailSender.send(message);} catch (MessagingException e) {e.printStackTrace();}}

使用了MimeMessageHelper简化了配置,构造方法的第二个参数true 表示构造一个multipart message 类型的邮件,此类型的邮件包含了多个正文、附件等,最后在单元测试进行测试:

 @Testpublic void sendAttachFileMail() {mailService.sendAttachFileMail("1739994163@qq.com","qazjou@163.com","测试邮件主题","测试邮件内容",new File("C:\\Users\\17399\\Desktop\\1.txt"));}

运行后可见发送成功了。

有的邮件正文可能要插入图片,此时就不是附件的内容了,使用FileSystemResource实现功能:

public void sendMailWithImg(String from, String to,String subject, String content,String[] srcPath,String[] resIds) {if (srcPath.length != resIds.length) {System.out.println("发送失败");return;}try {MimeMessage message = javaMailSender.createMimeMessage();MimeMessageHelper helper =new MimeMessageHelper(message,true);helper.setFrom(from);helper.setTo(to);helper.setSubject(subject);helper.setText(content,true);for (int i = 0; i < srcPath.length; i++) {FileSystemResource res =new FileSystemResource(new File(srcPath[i]));helper.addInline(resIds[i], res);}javaMailSender.send(message);} catch (MessagingException e) {System.out.println("发送失败");}}

在发送邮件时候分别传入图片资源路径和资源id,构造静态资源,然后调用addInline方法将资源加入邮件对象中。注意,在调用setText方法时候,第二个参数true 表示邮件正文是HTML格式的,该参数默认为false。

@Testpublic void sendMailWithImg() {mailService.sendMailWithImg("1739994163@qq.com","qazjou@163.com","测试邮件主题(图片)","<div>hello,这是一封带图片资源的邮件:" +"这是图片1:<div><img src='cid:p01'/></div>" +"这是图片2:<div><img src='cid:p02'/></div>" +"</div>",new String[]{"C:\\Users\\17399\\Desktop\\2.png","C:\\Users\\17399\\Desktop\\3.png"},new String[]{"p01", "p02"});}

运行结果如图:

定时任务

@Scheduled

定时任务是企业开发最常见的功能之一,如定时统计订单数、数据库备份、定时发送短信和邮件、定时统计博客访客。简单的定时任务直接通过@Scheduled 注解来实现,复杂的定时任务集成Quartz来实现。关于Quartz 可以看我这篇博客,Quartz任务

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>

开启定时任务:
在项目启动类添加注解开启定时任务:

@EnableScheduling
@SpringBootApplication

定时任务主要 @Component 来进行配置:

@Component
public class MySchedule {// 来标注一个定时任务,其中fixedDelay=1000 表示在当前任务结束后1s开始另一个任务
// initialDelay=1000 表示首次执行的延迟时间@Scheduled(fixedDelay = 1000)public void fixedDelay() {System.out.println("fixedDelay:"+new Date());}@Scheduled(fixedRate = 2000)public void fixedRate() {System.out.println("fixedRate:"+new Date());}@Scheduled(initialDelay = 1000,fixedRate = 2000)public void initialDelay() {System.out.println("initialDelay:"+new Date());}
// 也可以使用cron 表达式,来表示任务每分钟执行一次@Scheduled(cron = "0 * * * * ?")public void cron() {System.out.println("cron:"+new Date());}
}

配置完成后启动项目即可,定时任务部分打印日志如下:

Quartz

是一个功能丰富的开源作业调度库,由Java写成,可以集成在任何Java程序中,使用Quartz可以创建简单或者复杂的执行任务,支持数据库、集群、插件等,并且支持cron 表达式,有很高的灵活性,Spring Boot集成Quartz和Spring 集成非常类似,主要提供三个Bean: JobDetail,Trigger以及SchedulerFactory.

创建依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId></dependency>

然后创建两个Job:
MyFirstJob.java:

import org.springframework.stereotype.Component;import java.util.Date;@Component
public class MyFirstJob {public void sayHello() {System.out.println("MyFirstJob:sayHello:"+new Date());}
}

MySecondJob.java:

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.scheduling.quartz.QuartzJobBean;import java.util.Date;public class MySecondJob extends QuartzJobBean {private String name;public void setName(String name) {this.name = name;}@Overrideprotected void executeInternal(JobExecutionContext context){System.out.println("hello:"+name+":"+new Date());}
}

Job可以一个普通的JavaBean,如果是普通的JavaBean,那么可以添加@Component注解将之注册到Spring 容器中。当然也可以使用继承抽象类QuartzJobBean,则需要实现该类的executeInternal 方法,在任务被调用时候使用。接下来创建对JobDetail和Trigger 进行配置:

@Configuration
public class QuartzConfig {// 两种方法配置JobDetail,只需要指定Job的实例名和要调用的方法即可,
// 注册这种方法无法在创建JobDetail 传递参数@BeanMethodInvokingJobDetailFactoryBean jobDetail1() {MethodInvokingJobDetailFactoryBean bean =new MethodInvokingJobDetailFactoryBean();bean.setTargetBeanName("myFirstJob");bean.setTargetMethod("sayHello");return bean;}
// 指定JobClass 即可,通过JobDtaMap 传递参数到Job中,Job只需要提供属性名和set@BeanJobDetailFactoryBean jobDetail2() {JobDetailFactoryBean bean = new JobDetailFactoryBean();bean.setJobClass(MySecondJob.class);JobDataMap jobDataMap = new JobDataMap();jobDataMap.put("name","sang");bean.setJobDataMap(jobDataMap);bean.setDurability(true);return bean;}
// Trigger 有不同的实现方法,这里展示常用的Trigger:
// 这里设置JobDetail ,通过setRepeatCount 配置任务循环次数@BeanSimpleTriggerFactoryBean simpleTrigger() {SimpleTriggerFactoryBean bean =new SimpleTriggerFactoryBean();bean.setJobDetail(jobDetail1().getObject());bean.setRepeatCount(3);
// 启动延迟时间bean.setStartDelay(1000);
// 任务时间间隔bean.setRepeatInterval(2000);return bean;}
// 主要配置JobDetail 和Cron 表达式@BeanCronTriggerFactoryBean cronTrigger() {CronTriggerFactoryBean bean =new CronTriggerFactoryBean();bean.setJobDetail(jobDetail2().getObject());bean.setCronExpression("* * * * * ?");return bean;}@BeanSchedulerFactoryBean schedulerFactory() {SchedulerFactoryBean bean = new SchedulerFactoryBean();SimpleTrigger simpleTrigger = simpleTrigger().getObject();CronTrigger cronTrigger = cronTrigger().getObject();bean.setTriggers(simpleTrigger,cronTrigger);return bean;}
}

配置完成后,便可以启动项目了,此时需要将前面在启动类的注解删去:

批处理

Spring Batch 是一个开源的、全面的、轻量级的批处理框架,通过给此可以实现强大的批处理应用程序的开发。Spring Batch 提供了记录/追踪,事务管理,作业处理统计等,可以结合定时任务发挥更大的作用。
也提供了ItemReader、ItemProcessor和Item Writer 来完成数据的读取,可将执行状态持久化到数据库中。下面通过简单的数据复制展示Spring Batch。

创建依赖以及数据库相关依赖:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-batch</artifactId></dependency>

添加数据库依赖是为了将批处理的执行状态持久化到数据库中,配置相应的信息:

spring.datasource.type= com.alibaba.druid.pool.DruidDataSource
#spring.datasource.url=jdbc:mysql:///ay_user
#spring.datasource.url=jdbc:mysql:///jpa
spring.datasource.url = jdbc:mysql:///ay_user
spring.datasource.username= root
spring.datasource.password=q1
# 表示项目启动时候创建数据表的SQL 脚本位置(由batch提供)
spring.datasource.schema = classpath:/org/springframework/batch/core/schema-mysql.sql
#  表示在项目启动时执行建表SQL
spring.batch.jdbc.initialize-schema=always
# 不自动执行,需要用户手动触发执行
spring.batch.job.enabled=false

接着在项目启动类上添加注解:

@EnableBatchProcessing
@SpringBootApplication

然后配置批处理:

@Configuration
public class CsvBatchJobConfig {// 注入三个对象进行备用,这里持久化的方案是JDBC@ResourceJobBuilderFactory jobBuilderFactory;@ResourceStepBuilderFactory stepBuilderFactory;@ResourceDataSource dataSource;
// 配置ItemReader,Batch 提供了常用的ItemReader@Bean@StepScopeFlatFileItemReader<MUser> itemReader() {// 加载一个普通文件的ItemReaderFlatFileItemReader<MUser> reader = new FlatFileItemReader<>();
// 第一行是标题所以要跳过reader.setLinesToSkip(1);
// 找到配置的位置,此时已经写了,放在classpath目录下了reader.setResource(new ClassPathResource("data.csv"));
// 设置每一行的信息reader.setLineMapper(new DefaultLineMapper<MUser>(){{setLineTokenizer(new DelimitedLineTokenizer(){{setNames("id","username","address","gender");
// 配置列与列之间的间隔符setDelimiter("\t");}});
// 设置要映射的实体属性setFieldSetMapper(new BeanWrapperFieldSetMapper<MUser>(){{setTargetType(MUser.class);}});}});return reader;}
// 数据的写出逻辑@BeanJdbcBatchItemWriter jdbcBatchItemWriter() {JdbcBatchItemWriter writer = new JdbcBatchItemWriter();writer.setDataSource(dataSource);
// 注意占位符的写法: :属性名writer.setSql("insert into muser(id,username,address,gender) " +"values(:id,:username,:address,:gender)");writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>());return writer;}
// 配置一个Step,通过get来获取一个StepBuilder@BeanStep csvStep() {return stepBuilderFactory.get("csvStep").<MUser, MUser>chunk(2).reader(itemReader()).writer(jdbcBatchItemWriter()).build();}@BeanJob csvJob() {return jobBuilderFactory.get("csvJob").start(csvStep()).build();}
}

这里涉及了一个MUser实体类:

public class MUser {private Integer id;private String username;private String address;private String gender;
// 省略set 和get 方法
}

此时在classpath 下的data.csv文件如下:

现在创建一个Controller ,当用户发起请求时候触发批处理:

注意此时jobLauncher.run括号的第二个参数不要写出null 否则会报错,直接生成new对象即可。

@RestController
public class BatchController {@ResourceJobLauncher jobLauncher;@ResourceJob job;@GetMapping("/testbatch")public String batchtest() {try{// 框架提供,job是刚刚配置的,调用run方法来批处理jobLauncher.run(job,new JobParameters());}catch (Exception e) {e.printStackTrace();}return "success";}
}

最后运行以后,会在batch库中创建多个批处理相关的库,这些表是用来记录批处理的执行状态,此时data.csv数据成功插入user 表。:

此时查看数据,已经成功插入表中:

Swagger 2

前后端分离开发中,为了减少沟通成本,一般需要构建RESTful API 文档来描述所有的接口信息,但仍有一些弊端,说明:

  1. 接口众多,编写RESTful API 文档工作量大,因为要包含接口的基本信息,例如接口地址、接口请求参数、接口返回值等。
  2. 维护不方便,一旦接口发生了变化,需要修改文档
  3. 接口测试不方便,只能借助于第三方工具,例如Postman来测试。

Swagger 2是一个开源软件框架,可以帮助开发人进行设计、构建、记录和使用等,将代码和文档融为一体,可以完美解决上述问题,可以较好地集中在业务当中,同时也可以非常好整合在Spring Boot.

用常规的依赖太老了,不兼容我2.7版本的Spring Boot。

# 这个依赖更好,可以Swagger 2的兼容(可以比较方便的做升级了)
<dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version></dependency>

创建Swagger2 的配置类:

还有一种办法就是可以在启动类上面,加@EnableOpenApi 就直接可以运行了。(这个方法非常简单)

@Configuration
@EnableSwagger2
public class SwaggerConfig  implements WebMvcConfigurer {@Bean
// Docket is a builder which is intended to be the primary interface into the swagger-springmvc framework. Docket docket() {return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.basePackage("com.example.testspringboot.controller")).paths(PathSelectors.any()) // 所有包.build().apiInfo(new ApiInfoBuilder().description("接口文档").contact(new Contact("Jacin","baidu.com","1@qq.com")).version("v1.0").title("测试文档").license("Apache").licenseUrl("apache.org").build());}
}

通过@EnableSwagge2 注解开启 Swagger 2 ,主要是配置一个Docket,通过apis方法配置要扫描的controller 位置,通过paths 方法配置路径。在apiInfo 构建文档的基本信息,例如描述、联系人信息、版本等。
Swagger 2配置完成后,接下来开发接口了:

@RestController
//用来描述整个Controller 信息,默认就是类名
@Api(tags="用户接口")
public class SwaggerController {// 用在方法上,描述一个信息,value 是简单的描述,notes 用来备注方法的详细作用@ApiOperation(value = "查询用户",notes = "根据id")
// 方法上,描述方法的参数
// paramType 是方法参数的类型,可选值有path( PathVariable),query(@RequestParam)
// name 表示参数名称,和下面的参数变量对应
//  value 是参数描述信息,required 吧iOS是否必填   @ApiImplicitParam(paramType = "path",name="id",value = "用户id",required = true)@GetMapping("/user/{id}")public String getUserById(@PathVariable Integer id) {return "/user/"+ id;}
// 这个是响应结果的描述,code 是响应码,message是描述信息,有多个那么就放在一个里面@ApiResponses({@ApiResponse(code = 200,message = "删除成功"),@ApiResponse(code = 500,message = "删除失败")})@ApiOperation(value = "删除用户",notes = "id删除")@DeleteMapping("/user/{id}")public Integer deletUserById(@PathVariable Integer id) {return  id;}@ApiOperation(value = "添加用户",notes = "添加一个用户,传入用户名和地址")@ApiImplicitParams({@ApiImplicitParam (paramType = "query" ,name = "username",value = "用户名",required = true,defaultValue = "sang"),@ApiImplicitParam(paramType = "query" ,name = "address",value = "用户地址",required = true,defaultValue = "sang")})@PostMapping("/user")public String addUser(@RequestParam String username,@RequestParam String address) {return username + ":" + address;}@ApiOperation(value = "修改用户",notes = "修改用户,传入用户信息")
//    @PostMapping("/user")
//    public String updateUser(@RequestBody MUser user) {//        return user.toString();
//    }@GetMapping("/ignore")
// 不对某个接口生成文档@ApiIgnorepublic void ingoreMethod() {}@GetMapping("tst")public String tst() {return "tst";}
}

此时输入:http://localhost:8081/swagger-ui/index.html# (注意新版的格式

这次更新,移除了原来默认的swagger页面路径:http://host/context-path/swagger-ui.html,新增了两个可访问路径:http://host/context-path/swagger-ui/index.html和http://host/context-path/swagger-ui/

不过我一开始使用的是依赖是 (以下方法不一定成功)

 <dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>3.0.0</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>3.0.0</version></dependency>

而且用的http://localhost:8081/swagger-ui/ 发现怎么都进不去,一直报error.
查看swagger-ui 位置:

此时构造一个Webconfig 来实现 WebMvcConfigurer 接口方法:

    @Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {// 解决静态资源⽆法访问registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");// 解决swagger⽆法访问registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");// 解决swagger的js⽂件⽆法访问registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");}

不过我这里确实没实现出来,应该是spring boot 版本问题。毕竟swagger2 太久没更新了,所以使用新版的swagger2。

以上。

[Spring Boot 6]企业级开发相关推荐

  1. Kotlin + Spring Boot: 下一代企业级开发

    Kotlin + Spring Boot: 下一代企业级开发 前端 React + UXCore (使用 nowa)学习 Jietu20171210-022532.jpg Jietu20171210- ...

  2. Spring Boot 集成 WebFlux 开发 Reactive Web 应用

    Spring Boot 集成 WebFlux 开发 Reactive Web 应用 <Spring Boot 实战开发>-- 基于 Gradle + Kotlin的企业级应用开发最佳实践 ...

  3. 6.3 Spring Boot集成mongodb开发

    6.3 Spring Boot集成mongodb开发 本章我们通过SpringBoot集成mongodb,Java,Kotlin开发一个极简社区文章博客系统. 0 mongodb简介 Mongo 的主 ...

  4. Spring Boot常见企业开发场景应用、自动配置原理结构分析

    读者应具备: Spring SpringMVC服务器端开发基础 Maven基础 本篇主要介绍Spring Boot在企业开发中常见场景的使用.以及Spring Boot的基本原理结构. 以下为本篇设计 ...

  5. Spring Boot(5) web开发(3)拦截器、文件上传、异常处理

    Spring Boot(5) web开发(3)拦截器.文件上传.异常处理 学习视频: https://www.bilibili.com/video/BV19K4y1L7MT?p=49&spm_ ...

  6. Spring boot实训开发个人博客(二)详情页

    Spring boot实训开发个人博客(二)详情页 1.在index页面添加归档: 2.开始写详情页: 1.头部文件: 2.添加文章内容 <h2 class="ui center al ...

  7. JavaEE + Oracle + Spring Boot + Mybatis + Maven 开发简易版银行管理系统(附源码)

    文章目录 JavaEE + Oracle + Spring Boot + Mybatis + Maven 开发简易版银行管理系统 一.系统介绍 二.开发步骤 三.系统展示 四.开发总结 五.数据库相关 ...

  8. Spring boot集成axis2开发webservice 服务

    Spring boot集成axis2开发webservice 服务 1.新建Spring boot 项目 此处省略... 项目结构如下: 2.添加Axis2依赖 <!--axis2版本信息--& ...

  9. Spring Boot之Web开发

    Web开发 简单了解 快速上手 JSON 的支持 RESTful 的支持 支持热部署 快速上手 拦截器与过滤器 自定义过滤器 示例 自定义拦截器 示例 打包部署 多环境配置 服务器配置 项目打包 简单 ...

最新文章

  1. Ruby的Singleton method
  2. [java]键盘录入数值到数组-根据数值获取角标-找出数据中最大数值-反转数组
  3. nginx 和 uWISG 服务器之间如何配合工作的
  4. 精通 RPM 之查询篇
  5. 【51单片机快速入门指南】6.3:DS18B20 单总线数字温度计的多路读取
  6. LogBack 入门实践
  7. android fragment framelayout,framelayout中fragment的切换
  8. python里面的import provide_Python importorror:没有名为wmi的模块
  9. 云图说 | 3分钟创建一个游戏类工作负载
  10. 如何在应用系统中实现数据权限的控制功能
  11. WebRTC之RFC协议下载(八)
  12. Code::Blocks IDE - Open Source, Cross-platform Free C++ IDE
  13. 恒强制版系统980_华为mate40将首批升级鸿蒙2.0系统?
  14. 周立功串口服务器维修方法,周立功医生| 告诉你RS485通信的小秘密
  15. Java中super()的用法。主类和子类的继承关系。(2)
  16. 基于WEB的网上购物系统的设计与实现(附:源码 论文 sql文件)
  17. xhEditor用法
  18. 你真的理解了MVC, MVP, MVVM吗?
  19. 社区人物志|李昊鹏:日拱一卒,功不唐捐
  20. 今天开始学JAVA了!

热门文章

  1. 语音识别芯片的基本原理和工作流程
  2. 数据分析中的严谨问题
  3. Qt开发基础(7)——QImage与Mat之间的相互转换
  4. 帝国 loginjs.php,帝国cms JS调用登陆模板制作教程
  5. 泽塔云:用超融合撑起软件定义数据中心的梦想
  6. python批量检索文献_基于Python的文献检索系统设计与实现
  7. spark封神之路(7)-RDD算子详解第一部分
  8. 反编译“微软纸牌集合”资源文件
  9. 高等数学-线性代数:已知特征值,求解特征空间的特征向量
  10. 【MFC】打砖块小游戏(下)(7)