SpringBoot从入门到精通教程(二十九)- 微信企业支付集成(五分钟集成)
需求背景
SpringBoot用法:微信企业支付集成(五分钟集成)
问题痛点
通过SpringBoot框架,集成服务端微信企业支付接口,做到下载即用(填写好相关微信支付后台相关Key信息),最快五分钟集成成功,节省时间,同时也避免重复采坑。你也可能在此基础上优化代码,或者二次开发,希望对你有用。
- 目前这套代码在生产环境中运行超过一年时间,已成功给用户支付打款超过五千万,长期稳定运行,经过了线上长时间的验证。
业务场景
微信的入账通知:
技术点
1. 集成http依赖组件,调用微信官方支付接口
<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpmime</artifactId><version>4.5.6</version>
</dependency>
2. 集成xml依赖组件,处理xml内容
<dependency><groupId>jdom</groupId><artifactId>jdom</artifactId><version>1.1</version>
</dependency>
代码演示
1. 项目目录结构
2. pom.xml依赖组件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.md</groupId><artifactId>spring-boot2-parent</artifactId><version>0.0.1-SNAPSHOT</version><relativePath>../pom.xml</relativePath></parent><artifactId>spring-boot2-wechat-pay</artifactId><packaging>jar</packaging><name>spring-boot2-wechat-pay</name><description>Spring Boot, MVC, Rest API for App</description><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!-- 构建成可运行的Web项目 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>net.sf.json-lib</groupId><artifactId>json-lib-ext-spring</artifactId></dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpmime</artifactId></dependency><dependency><groupId>jdom</groupId><artifactId>jdom</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>swagger-bootstrap-ui</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
3. 支付服务工具类
PayServiceUtils:
package com.md.demo.pay.utils.variation;import java.net.InetAddress;
import java.util.Collections;
import java.util.Map;
import java.util.SortedMap;import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.md.demo.pay.utils.ConfigUtil;
import com.md.demo.pay.utils.FsClientWithCertSSL;
import com.md.demo.pay.utils.MapUtils;
import com.md.demo.pay.utils.PayCommonUtil;
import com.md.demo.pay.utils.StrUtil;
import com.md.demo.pay.utils.XMLUtil;
import com.md.demo.pay.utils.vo.PayWithdrawHis;import lombok.extern.slf4j.Slf4j;/*** 支付服务工具类* * @author Minbo**/
@Slf4j
public class PayServiceUtils {/*** 打款*/public static int doEpay(String appId, String openId, String ip, String amount, String orderId, String re_user_name,PayWithdrawHis objInfo, String projectFlag, String appIdName) {if (Strings.isNullOrEmpty(orderId) || Strings.isNullOrEmpty(openId)) {log.info("企业支付开始,openid: " + openId + " orderId: " + orderId);return 1;}int amountInt = Integer.valueOf(amount).intValue();if (amountInt < 100 || Strings.isNullOrEmpty(amount)) {log.warn("企业支付开始=> 金额不得少于100分(1元)");return 2;}try {if (Strings.isNullOrEmpty(ip)) {InetAddress addr = InetAddress.getLocalHost();ip = addr.getHostAddress().toString();}log.info("企业支付开始,用户:" + openId + " 订单号:" + orderId + " 金额:" + amount + ", ip=" + ip);// 设置支付参数SortedMap<String, Object> parameters = getSignParams(appId, orderId, openId, amountInt, ip, re_user_name,projectFlag, appIdName);parameters.put("sign", PayCommonUtil.createSignPublic(Charsets.UTF_8.toString(), parameters, projectFlag));// sign签名String requestXML = PayCommonUtil.getRequestXml(parameters);// 生成xml格式字符串String responseStr = FsClientWithCertSSL.doPost(ConfigUtil.PROMOTION_URL, requestXML, projectFlag);// 解析结果Map<String, Object> resutlMap = XMLUtil.doXMLParse(responseStr);log.info("企业付款,微信返回结果:resutlMap=" + resutlMap.toString());// 校验响应结果return_codeif (resutlMap != null && WeixinConstant.FAIL.equalsIgnoreCase(resutlMap.get("return_code").toString())) {log.error("企业支付调用失败");return 3;}if (StrUtil.null2Str(resutlMap.get("err_code")).equals("SENDNUM_LIMIT")) {return 66;}if (StrUtil.null2Str(resutlMap.get("err_code")).equals("NOTENOUGH")) {return 99;}if (StrUtil.null2Str(resutlMap.get("err_code")).equals("SYSTEMERROR")&& StrUtil.null2Str(resutlMap.get("err_code_des")).equals("系统繁忙,请稍后再试")) {return 77;}if (StrUtil.null2Str(resutlMap.get("err_code")).equals("SYSTEMERROR")) {return 88;}if (resutlMap != null && WeixinConstant.FAIL.equals(resutlMap.get("result_code"))) {log.error("企业付款失败:orderId=" + orderId + ",失败原因:return_msg=" + resutlMap.get("return_msg") + ","+ objInfo.toString());// 真实姓名不一致// 非实名用户账号不可发放// 用户账号被冻结,无法付款if (StrUtil.null2Str(resutlMap.get("err_code")).equals("NAME_MISMATCH")|| StrUtil.null2Str(resutlMap.get("err_code")).equals("V2_ACCOUNT_SIMPLE_BAN")|| StrUtil.null2Str(resutlMap.get("err_code")).equals("NO_AUTH")) {if (objInfo != null) {objInfo.setFailErrorMsg(resutlMap.get("err_code").toString() + ":" + resutlMap.get("err_code_des").toString());objInfo.setOrderStatusName("提现失败,金币已返还--" + resutlMap.get("err_code_des").toString());return 4;}// 参数错误} else if (StrUtil.null2Str(resutlMap.get("err_code")).equals("PARAM_ERROR")) {if (objInfo != null) {objInfo.setFailErrorMsg(resutlMap.get("err_code").toString() + ":" + resutlMap.get("err_code_des").toString());objInfo.setOrderStatusName("提现失败,金币已返还--" + "支付失败,请重新申请");return 4;}}String return_msg = resutlMap.get("return_msg").toString();if (return_msg.equals("openid与商户appid不匹配")) {log.error("提现失败【准备重试】,openid与商户appid不匹配。" + objInfo.toString());return 44;}return 55;}if (WeixinConstant.SUCCESS.equalsIgnoreCase(resutlMap.get("result_code").toString())) {Map<String, Object> map = buildClientJson(resutlMap);log.info("企业付款成功:" + map.toString());return 0;}} catch (Exception e) {log.error("企业付款异常:" + e.getMessage(), e);return 5;}return 1;}/*** 查询指定订单id状态*/public static int doEquery(String appId, String outOrderNo, String projectFlag) {if (Strings.isNullOrEmpty(outOrderNo)) {return 1;}try {// 组装查询参数SortedMap<String, Object> params = buildQueryParams(appId, outOrderNo, projectFlag);String requestXML = PayCommonUtil.getRequestXml(params);// 生成xml格式字符串// 带上post请求支付查询接口String responseStr = FsClientWithCertSSL.doPost(ConfigUtil.PROMOTION_QUERY_URL, requestXML, projectFlag);SortedMap<String, Object> responseMap = XMLUtil.doXMLParse(responseStr);// 解析响应xml格式字符串log.info("企业付款查询=> " + responseMap.toString());// 校验响应结果return_codeif (WeixinConstant.FAIL.equalsIgnoreCase(responseMap.get("return_code").toString())) {log.error("企业支付调用失败");return 2;}// 校验业务结果result_codeif (WeixinConstant.FAIL.equalsIgnoreCase(responseMap.get("result_code").toString())) {if ("NOT_FOUND".equalsIgnoreCase(responseMap.get("err_code").toString())) {log.info("指定单号数据不存在,放行打款...");return 4;}return 3;}if (StrUtil.null2Str(responseMap.get("err_code")).equals("SYSTEMERROR")&& StrUtil.null2Str(responseMap.get("status")).equals("PROCESSING")) {// 扣款消耗失败,退回提现if (StrUtil.null2Str(responseMap.get("reason")).equals("consumefund failed.")&& StrUtil.null2Str(responseMap.get("return_msg")).equals("consumefund failed.")) {log.error("扣款消耗失败,consumefund failed,退回提现");return 99;}return 77;}if (StrUtil.null2Str(responseMap.get("err_code")).equals("SYSTEMERROR")) {return 88;}if (WeixinConstant.SUCCESS.equalsIgnoreCase(responseMap.get("result_code").toString())) {log.error("已打款,不能重复打款");return 0;}// 组装响应数据Map<String, Object> resultMap = PayServiceUtils.buildResponse(responseMap);log.info("企业付款查询结果:" + resultMap.toString());return -1;} catch (Exception e) {log.error("付款查询异常:" + e.getMessage(), e);return 5;}}/*** 组装响应数据* * @param resutlMap 付款响应结果* @return*/private static Map<String, Object> buildClientJson(Map<String, Object> resutlMap) {if (resutlMap == null || resutlMap.isEmpty()) {return Collections.emptyMap();}Map<String, Object> returnMap = ImmutableMap.<String, Object>builder().put("trade_no", resutlMap.get("partner_trade_no")).put("payment_no", resutlMap.get("payment_no")).put("payment_time", resutlMap.get("payment_time")).put("result_msg", resutlMap.get("result_code")).build();return returnMap;}/*** 组合sign签名参数* * @return*/private static SortedMap<String, Object> getSignParams(String appId, String tradeNo, String openId, int amount,String ip, String re_user_name, String projectFlag, String appDesc) {
// String appId = WeixinPayConfig.APPID;String mchId = WeixinPayConfig.MCHID;Map<String, Object> oparams = ImmutableMap.<String, Object>builder().put("mch_appid", appId)
// .put("desc", WeixinConstant.EPAY_DESC).put("desc", appDesc)// 企业付款描述信息.put("mchid", mchId)// 商户号.put("nonce_str", PayCommonUtil.CreateNoncestr())// 16随机字符串(大小写字母加数字).put("device_info", PayCommonUtil.createConceStr(32).toUpperCase())// 设备号 暂时写死.put("partner_trade_no", tradeNo)// 商户订单号.put("openid", openId)// 用户openid 注意:微信的openid.put("check_name", "NO_CHECK")// 不校验真实姓名
// .put("check_name", "FORCE_CHECK")// 校验真实姓名
// .put("re_user_name", re_user_name)// 校验真实姓名.put("amount", amount)// 金额.put("spbill_create_ip", ip)// ip地址.build();return MapUtils.sortMap(oparams);}/*** 封装查询结果数据* * @param responseMap 查询结果* @return*/private static Map<String, Object> buildResponse(SortedMap<String, Object> responseMap) {Map<String, Object> returnMap = ImmutableMap.<String, Object>builder().put("trade_no", responseMap.get("partner_trade_no")).put("payment_no", responseMap.get("detail_id")).put("payment_account", responseMap.get("payment_amount")).put("transfer_time", responseMap.get("transfer_time")).put("result_code", responseMap.get("result_code")).build();return returnMap;}/*** 组装查询参数* * @param outTradeNo* @return*/private static SortedMap<String, Object> buildQueryParams(String appId, String outTradeNo, String projectFlag) {String mchId = WeixinPayConfig.MCHID;// 组装查询参数- 可以使用treemapMap<String, Object> queryParams = ImmutableMap.<String, Object>builder().put("appid", appId)// 商户号的appid.put("mch_id", mchId)// 商户号.put("nonce_str", PayCommonUtil.CreateNoncestr())// 16随机字符串(大小写字母加数字).put("partner_trade_no", outTradeNo)// 商户订单号.build();// key ASCII 排序SortedMap<String, Object> sortMap = MapUtils.sortMap(queryParams);// MD5签名String createSign = PayCommonUtil.createSignPublic(Charsets.UTF_8.toString(), sortMap, projectFlag);sortMap.put("sign", createSign);return sortMap;}
}
4. 填写相关微信后台支付Key信息
这里目前是写到了代码中,因为一般很少变化。当然,你也可以做成配置信息,或者使用配置中心,我们最新代码已上线配置中心Nacos。
WeixinConstant:
package com.md.demo.pay.utils.variation;/*** 微信支付常量类*/
public class WeixinConstant {public static String SUCCESS = "SUCCESS"; // 成功return_codepublic static String FAIL = "FAIL"; // 失败return_code// public static String EPAY_DESC = "申请提现转账";// 企业付款描述
}
WeixinPayConfig:
package com.md.demo.pay.utils.variation;/*** 微信公众号支付配置信息*/
public class WeixinPayConfig {// // APPID(可固化,也可以动态变,DB中可同时记录AppID)
// public final static String APPID = "xxxxxx";// 公众平台商户IDpublic final static String MCHID = "xxxxxx";// 公众平台商户KEYpublic final static String KEY = "xxxxxx";// 微信企业支付证书public static String CERT_FILE = System.getProperty("user.dir") + System.getProperty("file.separator") + "cert"+ System.getProperty("file.separator") + "apiclient_cert.p12";
}
此项目中,只需要更改这几个文件的信息就可以了。
最后把证书放到项目下的cert/目录下即可,例如:
5. 提现记录实体类
PayWithdrawHis:
package com.md.demo.pay.utils.vo;import java.io.Serializable;
import java.util.Date;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;/*** <p>* 提现记录* </p>** @author minbo*/
@Data
@ApiModel(value = "PayWithdrawHis对象", description = "提现记录")
public class PayWithdrawHis implements Serializable {private static final long serialVersionUID = 1L;@ApiModelProperty(value = "订单流水号")private String orderId;@ApiModelProperty(value = "系统用户ID")private String sysUserId;@ApiModelProperty(value = "AppID")private String appId;@ApiModelProperty(value = "兑换金币值")private String gold;@ApiModelProperty(value = "提现金额")private String income;@ApiModelProperty(value = "提现申请时间")private Date applyTime;@ApiModelProperty(value = "订单状态类型值,1/2/3/4")private Integer orderStatus;@ApiModelProperty(value = "订单状态类型名1处理中,2提现成功,3审核中,4提现失败")private String orderStatusName;@ApiModelProperty(value = "微信openID")private String wechatOpenId;@ApiModelProperty(value = "提现完成时间")private Date finishedTime;@ApiModelProperty(value = "提现失败原因")private String failErrorMsg;}
6. 支付服务接口
IWechatPayService:
package com.md.demo.service;/*** <p>* 微信支付打款服务* </p>** @author minbo*/
public interface IWechatPayService {/*** 微信批量打款*/public void wechatBatchPay();
}
WechatPayServiceImpl:
package com.md.demo.service.impl;import java.util.ArrayList;
import java.util.List;import org.springframework.stereotype.Service;import com.md.demo.pay.utils.StrUtil;
import com.md.demo.pay.utils.variation.PayServiceUtils;
import com.md.demo.pay.utils.vo.PayWithdrawHis;
import com.md.demo.service.IWechatPayService;import lombok.extern.slf4j.Slf4j;/*** <p>* 服务实现类* </p>** @author minbo*/
@Service
@Slf4j
public class WechatPayServiceImpl implements IWechatPayService {/*** 悦头条-新版-微信批量打款*/@Overridepublic void wechatBatchPay() {// TODO 这里可从DB中获得订单数据List<PayWithdrawHis> list = new ArrayList<PayWithdrawHis>();loopFlag: for (PayWithdrawHis objInfo : list) {log.info("---------------------------------------------");log.info("--------------------start--------------------");try {// 这里的openId,是当前应用或公众号下的openIdString openId = StrUtil.null2Str(objInfo.getWechatOpenId());String ip = "0.0.0.0";// 提现金币,微信官方后台是分为单位,则要乘以100String amount = String.valueOf(Integer.valueOf(objInfo.getIncome()) * 100);// 订单IDString orderId = objInfo.getOrderId();// // 获得appId的配置,可以把支付信息等做成DB可配置项
// PayAppConfig sdkPayAppConfig = this.sdkPayAppConfigService.getById(objInfo.getAppId());
// String appIdName = sdkPayAppConfig.getProductName() + "的红包";
// String wxAppId = sdkPayAppConfig.getWxAppId();// 企业付款描述String appIdName = "填入账信息详情展示"; // 例如:XXX产品的红包String wxAppId = "微信后台的appId";// 1. 先查询订单的状态int flag = PayServiceUtils.doEquery(wxAppId, orderId, null);if (flag == 99) {log.warn("查询,扣款消耗失败,退回打款");// TODO 扣款消耗失败处理continue;}if (flag == 88) {log.error("查询,后台企业微信账户系统异常,停止打款", new RuntimeException("微信账户异常,停止此次打款"));break loopFlag;}if (flag == 77) {log.error("查询,后台企业微信账户系统订单正在处理中", new RuntimeException("微信账户订单正在处理中,跳过处理"));continue;}if (flag == 0) {log.info("之前已经打款成功,直接更新状态即可。");// TODO 已打款成功,不重复打款,直接更新状态处理continue;}// 不存在此订单数据,真正开始打款if (flag == 4) {log.info("开始真正打款,然后更新订单状态...");int result = PayServiceUtils.doEpay(wxAppId, openId, ip, amount, orderId, null, objInfo, null,appIdName);if (result == 99) {log.error("打款,后台企业微信账户支付余额不足,停止打款", new RuntimeException("余额不足,停止此次打款"));break loopFlag;}if (result == 88) {log.error("打款,后台企业微信账户系统异常,停止打款。" + objInfo.toString(), new RuntimeException("微信账户异常,停止此次打款"));break loopFlag;}if (result == 77) {log.error("打款速度过快,微信账户处理繁忙,休眠十秒。" + objInfo.toString());Thread.sleep(10000);continue;}if (result == 66) {log.error("该用户今日付款次数超过限制,如有需要请登录微信支付商户平台更改API安全配置。",new RuntimeException("该用户今日付款次数超过限制,跳过该用户的打款"));continue;}if (result == 55) {log.error("打款报错【微信】,请检查。" + objInfo.toString(), new RuntimeException("打款报错,跳过该用户打款"));continue;}// 最后结果处理if (result == 0) {// TODO 打款成功处理} else if (result == 4) {// TODO 打款失败处理}}} catch (Exception e) {log.error("提现记录异常:orderId=" + objInfo.getOrderId() + ", msg=" + e.getMessage(), e);}log.info("--------------------end--------------------");log.info("---------------------------------------------");}}}
7. 计划任务
ScheduledTasks:
package com.md.demo.task;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import com.md.demo.service.IWechatPayService;@Component
public class ScheduledTasks {protected static Logger logger = LoggerFactory.getLogger(ScheduledTasks.class);@Autowiredprivate IWechatPayService wechatEpayService;/*** 每60秒执行一次,从DB中获得订单数据*/@Scheduled(initialDelay = 5000, fixedDelay = 60000)public void httTaskOfWechatPay() {logger.info("------------------------------------------------");logger.info("============微信打款任务,start===================");// TODO 调用打款服务
// this.wechatEpayService.wechatBatchPayHtt();logger.info("============微信打款任务,end===================");logger.info("------------------------------------------------");}
}
8. 启动类
Application:
package com.md.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;/*** 程序主入口* * @author Minbo**/
@SpringBootApplication
@EnableScheduling
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}/*** 开启过滤器功能* * @return*/private CorsConfiguration buildConfig() {CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.addAllowedOrigin("*");corsConfiguration.addAllowedHeader("*");corsConfiguration.addAllowedMethod("*");return corsConfiguration;}/*** 跨域过滤器* * @return*/@Beanpublic CorsFilter corsFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", buildConfig());return new CorsFilter(source);}
}
本地测试
1. WechatPayServiceTest
package com.md.demo.pay.test;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;import com.md.demo.pay.utils.variation.PayServiceUtils;/*** 本地测试类* * @author Minbo**/
@Service
public class WechatPayServiceTest {protected static Logger logger = LoggerFactory.getLogger(WechatPayServiceTest.class);// 测试public static void main(String[] args) {// 订单查询
// wechat.doEquery("996846M20180330115059R5X9H9MHM", null);// 支付打款PayServiceUtils.doEpay("wx3ff92cec62c097d1", "o7Hzn0rr6U3Xb7Q1W9M-bH7SQXys", "192.168.1.1", "100","996846M20180330115059R5X9H9MHY", null, null, null, "xxx的红包");System.out.println("done");}}
如果提示例如“AppID和商户号不匹配”,“微信openId与AppID不匹配”,“本地IP不允许调用支付接口”,“余额不足”等提示,则证明已经集成成功。像“本地IP不允许调用支付接口”,只需要把本地电脑的公网IP配置到微信后台即可。
完整源码下载
我的Github源码地址:
https://github.com/hemin1003/spring-boot-study/tree/master/spring-boot2-study/spring-boot2-parent/spring-boot2-wechat-pay
下一章教程
SpringBoot从入门到精通教程(三十)- 支付宝企业支付集成(五分钟集成)
该系列教程
SpringBoot从入门到精通教程
我的专栏
- SpringBoot系列专栏
- 高可用高并发实战专栏
- 微服务架构实战
- DevOps实战专栏
- 程序化广告实战专栏
至此,全部介绍就结束了
-------------------------------
-------------------------------
我的CSDN主页
关于我(个人域名)
我的开源项目集Github
期望和大家一起学习,一起成长,共勉,O(∩_∩)O谢谢
欢迎交流问题,可加个人QQ 469580884,
或者,加我的群号 751925591,一起探讨交流问题
不讲虚的,只做实干家
Talk is cheap,show me the code
SpringBoot从入门到精通教程(二十九)- 微信企业支付集成(五分钟集成)相关推荐
- SpringBoot从入门到精通教程(二十七)- @Valid注解用法详解+全局处理器Exception优雅处理参数验证用法
问题痛点 用 Spring 框架写代码时,写接口类,相信大家对该类的写法非常熟悉.在写接口时要写效验请求参数逻辑,这时候我们会常用做法是写大量的 if 与 if else 类似这样的代码来做判断,如下 ...
- SpringBoot从入门到精通教程
SpringBoot从入门到精通教程 一.来自ImportNew公众号的SpringBoot教程系列,可参考学习 SpringBoot (一) :入门篇--http://mp.weixin.qq.co ...
- SpringBoot从入门到精通教程(三十一)- 爬虫框架集成
需求背景 SpringBoot用法:爬虫框架集成 业务场景 以抓取"今日头条"新闻举例说明 技术点 1. 集成爬虫框架webmagic(更多了解webmagic,可以去官方地址) ...
- javaSE从入门到精通的二十万字总结(一)
目录 前言 1. 面向对象 1.1 final关键字 1.2 抽象类 1.3 接口 1.4 接口与抽象类结合 1.5 接口的作用 1.6 包和import 1.7 访问控制权限 1.8 Object类 ...
- SpringBoot从入门到精通教程(七):集成Redis
今天,我们继续讲SpringBoot整合Redis ,也就缓存,它将与我们的Springboot整合 Redis 简介 Redis 是当前比较热门的NOSQL系统之一,它是一个开源的使用ANSI c语 ...
- SpringBoot从入门到精通教程(八):集成ElasticSearch
本主要介绍ElasticSearch 和 SpringBoot 的整合 ,对您有帮助的话,点个关注哦 ElastSearch 介绍 ElasticSearch是一个基于Lucene的搜索服务器.它提供 ...
- Webpack入门教程二十九
133.在模板中以内联的形式引用script脚本,修改webpack.config.js文件,代码如下 var htmlWebpackPlugin = require("html-webpa ...
- javaSE从入门到精通的二十万字总结(二)
目录 前言 5. 集合 5.1 集合两大类 5.2 Collection 5.2.1 常用方法 5.2.2 迭代器 5.2.3 contains方法 5.2.4 remove方法 5.3 List 5 ...
- PreScan快速入门到精通第三十九讲基于车道线识别传感器的车道保持辅助算法Demo讲解
车道保持辅助系统介绍: 什么是车道保持辅助系统? 疲劳和分心是无意中偏离车辆行驶车道线的最常见原因.车道保持辅助系统主动帮助驾驶者将其车辆保持在车道内,避免或者降低事故的发生. 车道保持辅助系统使用一 ...
最新文章
- 进程创建函数fork()和vfork()
- 51CTO采访Cisco专家何凌:实现整体虚拟化
- 简单的MYSQL数据库
- Python3 字典
- gcc动态链接库基本知识 [转]
- java 异步写_Java异步编程实战
- python修改图像分辨率大小
- 互联网性能监测方法及工具
- 用74HC573进行LED数码管驱动的编程
- springBoot 修行之路1
- 中国涂料工业协会:世界十大涂料品牌2011年度报告
- 使用 Qt for Android 获取并利用手机传感器数据(下篇)使用C++实现功能
- c语言.jpg图片转成数组_AWTK——基于C语言开发的GUI框架,功能强大,支持跨平台同步开发...
- php图片文字水印透明度,php图片水印 可以设置透明度
- 亲密接触Redis-第三天(Redis的Load Balance)
- 51cto学院微信支付实战对接开发视频教程
- MXene纳米片Ti3C2水分散液的应用
- 统计学习方法|最大熵原理剖析及实现
- ROS2与C++入门教程-创建服务(srv)文件 - 创客智造
- Self-training with Noisy Student improves ImageNet classification论文阅读
热门文章
- AE教程丨星球文字环绕特效
- 中小学python编程教学-中小学青少年编程课程
- 在厦门,“隐藏”了一个超高逼格的智慧园区
- 机器学习笔记(3.1)
- 英语构词法-learning
- Ghost过程中出现GHOSTERR.TXT文件的解决方法
- sql2java-excel(一):基于apache poi实现数据库表的导出及支持spring web
- 618电商平台鏖战,天猫618才是绝对主场
- 用计算机管理从新分区,电脑如何分区硬盘分区_电脑怎么重新分区教程-win7之家...
- MATLAB plot 双纵轴颜色修改方法——适用于所有版本(不限于2019b及以上)