当前位置: 首页 > news >正文

Spring Cloud Gateway实战WebFlux解析请求体及抛出指定错误代码和信息

概述

基于Spring Cloud开发微服务时,使用Spring Cloud原生自带的Gateway作为网关,所有请求都需要经过网关服务转发。

为了防止恶意请求刷取数据,对于业务请求需要进行拦截,故而可在网关服务增加拦截过滤器。基于此,有如下源码:


@Slf4j
@Component
public class BlockListFilter extends AbstractGatewayFilterFactory {private static final String DIALOG_URI = "/dialog/nextQuestion";@Resourceprivate AssessmentBlockListService assessmentBlockListService;@Lazy@Resourceprivate RemoteUserService remoteUserService;@Lazy@Resourceprivate RemoteRcService remoteRcService;@Lazy@Autowiredprivate RemoteOAuthService remoteOAuthService;@Value("${blockListSwitch:true}")private Boolean blockListSwitch;@Overridepublic GatewayFilter apply(Object config) {return (exchange, chain) -> {ServerHttpResponse response = exchange.getResponse();response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);ServerHttpRequest serverHttpRequest = exchange.getRequest();String uri = serverHttpRequest.getURI().getPath();HttpHeaders httpHeaders = serverHttpRequest.getHeaders();// 只处理相关urlif (blockListSwitch && StringUtils.equalsIgnoreCase(uri, DIALOG_URI)) {String token = httpHeaders.getFirst("Authorization");String pureToken = StringUtils.replaceIgnoreCase(token, "Bearer ", "");BaseUserInfo baseUserInfo = UserUtil.getBaseUserInfo(pureToken);if (baseUserInfo == null) {log.warn("传入的token非法:{}", token);return chain.filter(exchange);}// 从JWT token中解析用户信息(不含mobile等敏感信息)String channel = baseUserInfo.getChannel();String userKey = baseUserInfo.getUserkey();UserParam userParam = new UserParam();userParam.setKey(userKey);userParam.setChannel(channel);// Feign远程请求user服务获取mobile信息Response<UserAccountVO> userAccountResponse = remoteUserService.baseQuery(userParam);if (userAccountResponse.getCode() != 0) {log.warn("未获取到userKey={}的用户信息!", userKey);return chain.filter(exchange);}UserAccountVO userAccountVO = userAccountResponse.getData();log.info("blocklist filter user={}", JsonUtil.beanToJson(userAccountVO));String mobile = userAccountVO.getMobile();if (StringUtils.isNotBlank(userKey)) {// 具体的拦截业务逻辑this.process(uri, userKey, mobile, channel, pureToken);}return chain.filter(exchange);}return chain.filter(exchange);};}private String process(String uri, String userKey, String mobile, String channel, String token) {DetectUserDTO detectUserDTO = new DetectUserDTO();detectUserDTO.setChannel(channel);detectUserDTO.setMobile(mobile);detectUserDTO.setUserKey(userKey);detectUserDTO.setUri(uri);Response<DetectUserVO> detectUserVOResponse = remoteRcService.detectUser(detectUserDTO);if (detectUserVOResponse.getCode() != 0) {log.warn("mobile={} 风控接口返回异常:{}", mobile, JsonUtil.beanToJson(detectUserVOResponse));return null;}DetectUserVO detectUserVO = detectUserVOResponse.getData();if (detectUserVO.getIsInAllowList()) {log.info("在白名单中,放行");} else if (detectUserVO.getIsInBlockList()) {log.info("在黑名单中,拦截处理...");this.logout(token);return "当前手机号问诊次数已达今日上限!";}return null;}private void logout(String token) {// 强制下线,踢出登录态LogoutDto logoutDto = new LogoutDto();logoutDto.setToken(pureToken);remoteOAuthService.logout(logoutDto);}
}

上面的代码只是实现:判断流量请求URL,进而判断用户是否触发风控黑名单机制,如果触发黑名单,则踢出登录态强制用户下线,即用户不能使用App。

需求

异常捕获

上面的踢出登录态做法过于简单粗暴,App有若干个功能模块,某些请求URL触发黑名单机制,就强制用户下线,不能使用App其他功能。应该允许用户使用除了触发黑名单模块的其他模块功能。当然,对于是否强制下线的交互设计,每个人有每个人的看法。

就我遇到的情况来细说,痛点在于,前端(所谓大前端概念,含iOS和安卓App)同学针对此种情况直接返回统一的错误页:【抱歉,请返回再试一次!】,并且这个页面没有任何UI设计,仅仅是前面提到的一句提示文案。用户看到这个文案,会重新登录(因为后端做了踢出登录态逻辑),App成功后经过网关校验,发现用户依旧触发黑名单,然后再次踢出登录态,陷入死循环。

基于此,做如下改造:不踢出登录态,可以继续使用其他模块功能,在使用某个触发黑名单机制的模块时后端返回错误码,前端弹窗提示【无法使用此功能】。

故而对上面的代码进行调整:

String msg = this.process(uri, userKey, mobile, channel, pureToken,);
if (StringUtils.isNotBlank(msg)) {return Mono.error(new CustomException(BlockTypeEnum.getCodeByMsg(msg), msg));
}

本地启动Gateway网关服务,postman模拟请求,得到接口返回数据:
在这里插入图片描述
咦?怎么Code不是枚举类里定义的错误文案msg对应的错误码,而是500呢?

看代码,发现有个继承DefaultErrorWebExceptionHandler的JsonErrorWebExceptionHandler类,那这个类也需要调整:


@Slf4j
public class JsonErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {public JsonErrorWebExceptionHandler(ErrorAttributes errorAttributes,ResourceProperties resourceProperties,ErrorProperties errorProperties,ApplicationContext applicationContext) {super(errorAttributes, resourceProperties, errorProperties, applicationContext);}@Overrideprotected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {Map<String, Object> errorAttributes = new HashMap<>(8);Throwable error = super.getError(request);errorAttributes.put("message", error.getMessage());if (error instanceof CustomException) {errorAttributes.put("code", ((CustomException) error).getCode());} else {errorAttributes.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());}errorAttributes.put("method", request.methodName());errorAttributes.put("path", request.path());log.warn("网关异常,path:{},method:{},message:{}", request.path(), request.methodName(), error.getMessage());return errorAttributes;}@Overrideprotected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);}@Overrideprotected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {// 这里其实可以根据errorAttributes里面的属性定制HTTP响应码return HttpStatus.INTERNAL_SERVER_ERROR;}
}

经过调整后,断点调试,万事大吉:
在这里插入图片描述
但是!!!

后来在和前端联调时才发现有点不对劲:
在这里插入图片描述
如上图,枚举类里有几个code,每个code都有对应的系统功能模块黑名单msg。之前没有注意到右上角的接口状态码是500,这个500表示请求未成功。也就是说,接口未成功,接口响应码是自定义的code也没啥用。status必须得是200、204、202这种才行。

总结一下:虽然使用Mono.error()返回业务自定义错误信息,但错误代码9996被转为500。

继续排查。找到下面参考2里的文章,做如下调整:

String msg = this.process(uri, userKey, mobile, channel, pureToken,);
if (StringUtils.isNotBlank(msg)) {// 对应200,表明接口请求是成功的,但是触发业务异常错误码exchange.getResponse().setStatusCode(HttpStatus.OK);return exchange.getResponse().writeWith(Flux.just(exchange.getResponse().bufferFactory().wrap(JSON.toJSONString(msg).getBytes())));
}

调试结果,右上角的Status变成200:
在这里插入图片描述
注意看,Postman可以返回中文msg。

发布测试环境后,在Chrome浏览器里却发现返回数据乱码!?
在这里插入图片描述
IDEA调整截图如上没有乱码问题,console控制台打印也是正常:
在这里插入图片描述
乱码不是什么大问题,研发这么多年遇到无数次。继续排查,做如下调整解决问题:

return exchange.getResponse().writeWith(Flux.just(exchange.getResponse().bufferFactory().wrap(JSON.toJSONString(msg).getBytes(StandardCharsets.UTF_8))));

获取请求体

前面提到,我们的App有几个功能模块,其中一个模块的请求全部dialog/nextQuestion接口。用户恶意刷数据,多次请求此接口就会触发黑名单。但是在我们的交互设计上,又希望用户从其他功能模块切换到此功能时,可以请求一次此接口。那怎么判断是第一次呢?那就需要解析requestBody。

根据下面的参考1文章,增加如下代码:

public String resolveBodyForDialog(ServerHttpRequest serverHttpRequest) {String uri = serverHttpRequest.getURI().getPath();// 只有某些请求才解析if (!StringUtils.equalsAnyIgnoreCase(uri, "dialog/nextQuestion")) {return "";}StringBuilder sb = new StringBuilder();Flux<DataBuffer> body = serverHttpRequest.getBody();body.subscribe(buffer -> {byte[] bytes = new byte[buffer.readableByteCount()];buffer.read(bytes);DataBufferUtils.release(buffer);String bodyString = new String(bytes, StandardCharsets.UTF_8);sb.append(bodyString);});return sb.toString();
}

本地调试时没有问题,可以获取到requestBody:
在这里插入图片描述
但是测试环境却又问题:
在这里插入图片描述
详细的错误日志:

500 Server Error for HTTP POST "/api/open/dialog/nextQuestion"
io.netty.handler.codec.EncoderException: io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1
at io.netty.handler.codec.MessageToMessageEncoder.write(MessageToMessageEncoder.java:107)
at io.netty.channel.CombinedChannelDuplexHandler.write(CombinedChannelDuplexHandler.java:348)
at io.netty.channel.AbstractChannelHandlerContext.invokeWrite0(AbstractChannelHandlerContext.java:716)
at io.netty.channel.AbstractChannelHandlerContext.invokeWrite(AbstractChannelHandlerContext.java:708)
at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:791)
at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:701)
at reactor.netty.channel.MonoSendMany$SendManyInner.run(MonoSendMany.java:286)
at reactor.netty.channel.MonoSendMany$SendManyInner.trySchedule(MonoSendMany.java:368)
at reactor.netty.channel.MonoSendMany$SendManyInner.onSubscribe(MonoSendMany.java:221)
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:90)
at reactor.core.publisher.FluxContextStart$ContextStartSubscriber.onSubscribe(FluxContextStart.java:97)
at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54)
at reactor.core.publisher.MonoSubscriberContext.subscribe(MonoSubscriberContext.java:47)
at reactor.core.publisher.FluxSourceMonoFuseable.subscribe(FluxSourceMonoFuseable.java:38)
at reactor.core.publisher.FluxMapFuseable.subscribe(FluxMapFuseable.java:63)
at reactor.core.publisher.Flux.subscribe(Flux.java:7921)
at reactor.netty.channel.MonoSendMany.subscribe(MonoSendMany.java:81)
at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:153)
at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56)
at reactor.core.publisher.Mono.subscribe(Mono.java:3848)
at reactor.netty.NettyOutbound.subscribe(NettyOutbound.java:305)
at reactor.core.publisher.MonoSource.subscribe(MonoSource.java:51)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
at reactor.netty.http.client.HttpClientConnect$HttpIOHandlerObserver.onStateChange(HttpClientConnect.java:441)
at reactor.netty.ReactorNetty$CompositeConnectionObserver.onStateChange(ReactorNetty.java:470)
at reactor.netty.resources.PooledConnectionProvider$DisposableAcquire.onStateChange(PooledConnectionProvider.java:512)
at reactor.netty.resources.PooledConnectionProvider$PooledConnection.onStateChange(PooledConnectionProvider.java:451)
at reactor.netty.channel.ChannelOperationsHandler.channelActive(ChannelOperationsHandler.java:62)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:225)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:211)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelActive(AbstractChannelHandlerContext.java:204)
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelActive(CombinedChannelDuplexHandler.java:414)
at io.netty.channel.ChannelInboundHandlerAdapter.channelActive(ChannelInboundHandlerAdapter.java:69)
at io.netty.channel.CombinedChannelDuplexHandler.channelActive(CombinedChannelDuplexHandler.java:213)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:225)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:211)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelActive(AbstractChannelHandlerContext.java:204)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelActive(DefaultChannelPipeline.java:1396)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:225)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:211)
at io.netty.channel.DefaultChannelPipeline.fireChannelActive(DefaultChannelPipeline.java:906)
at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.fulfillConnectPromise(AbstractEpollChannel.java:618)
at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.finishConnect(AbstractEpollChannel.java:651)
at io.netty.channel.epoll.AbstractEpollChannel$AbstractEpollUnsafe.epollOutReady(AbstractEpollChannel.java:527)
at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:422)
at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:333)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:906)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)

具体研究这篇文章,做如下调整:

String uri = serverHttpRequest.getURI().getPath();
// 只有某些请求才解析
if (!StringUtils.equalsAnyIgnoreCase(uri, DIALOG_URI)) {return "";
}
Flux<DataBuffer> body = serverHttpRequest.getBody();
AtomicReference<String> bodyRef = new AtomicReference<>();
body.subscribe(buffer -> {CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());bodyRef.set(charBuffer.toString());
});
return bodyRef.get();

上面的报错消失。问题虽然是解决,但是不明就里。

后面又发现一个乱码问题,针对如下requestBody:

{"stateId": "DASHBOARD","answer": {"transitionId": "GET_HEALTH_ADVICE","label": "开始评估症状"}
}

bodyRef.get()获取到的中文数据乱码。参考3,解决方法:

String encoding = System.getProperty("file.encoding");
CharBuffer charBuffer = Charset.forName(encoding).decode(buffer.asByteBuffer());
bodyRef.set(charBuffer.toString());

参考

  • Spring Cloud Gateway读取RequestBody数据
  • 使用Spring Cloud-Gateway WebFlux抛出指定错误代码和信息
  • 从String获得ByteBuffer、从ByteBuffer获得CharBuffer的正确姿势

相关文章:

Spring Cloud Gateway实战WebFlux解析请求体及抛出指定错误代码和信息

概述 基于Spring Cloud开发微服务时&#xff0c;使用Spring Cloud原生自带的Gateway作为网关&#xff0c;所有请求都需要经过网关服务转发。 为了防止恶意请求刷取数据&#xff0c;对于业务请求需要进行拦截&#xff0c;故而可在网关服务增加拦截过滤器。基于此&#xff0c;有…...

Servlet开发-通过代码案例熟悉HttpServletRequest类

关于Servlet开发的流程推荐看servlet开发-通过Tomcat部署一个简单的webapp Servlet开发与idea集成的插件安装推荐看idea集成tomcat&#xff08;Smart Tomcate插件安装&#xff09; postman&#xff08;第三方创建HTTP请求工具&#xff09;的安装推荐看创建HTTP请求的几种方式…...

离线环境harbor 搭建及使用

一 摘要 本文主要介绍harbor 的安装及使用。 二 环境信息及部署图 2.1 环境信息 名称版本备注操作系统centos7.9容器docker 23.0.1harbor2.7代理nginx待补充 2.2 架构图 说明&#xff1a; 1.harbor 核心服务里有个nginx &#xff0c;也可以用该nginx 做代理 2.proxy-ngin…...

华为杯数学建模比赛经验分享

再过一周左右,第二十届华为杯数学建模比赛就要开赛了&#xff0c;所以今天分享一下个人数学建模比赛的经验。 今天给大家分享一期关于华为杯数学建模比赛的经验分享&#xff0c;我将从以下三个方面展开说明&#xff1a; &#xff08;1&#xff09;如何准备数学建模比赛&#x…...

c语言 - 实现每隔1秒向文件中写入当前系统时间

实现思路 主要是通过库函数和结构体获取当前系统时间&#xff08;年月日和时分秒&#xff09;保存到变量里&#xff0c;然后通过格式化输出函数将当前系统时间输出到文件中去。 但是需要注意的是题目要求每隔 1 s对系统时间进行输出&#xff0c;所以需要加入 sleep()函数进行调…...

使用cpolar端口映射的方法轻松实现在Linux环境下SVN服务器的搭建与公网访问

文章目录 前言1. Ubuntu安装SVN服务2. 修改配置文件2.1 修改svnserve.conf文件2.2 修改passwd文件2.3 修改authz文件 3. 启动svn服务4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射本地端口 5. 测试公网访问6. 配置固定公网TCP端口地址6.1 保留一个固定的公网TCP端口地址6…...

前后台分离开发 YAPI平台 前端工程化之Vue-cli

目录 YAPI介绍前端工程化之Vue-cli前端工程化简介前端工程化入门——Vue-cli环境准备Vue项目简介创建Vue项目vue项目目录结构介绍vue项目运行方法 Vue项目开发流程 前后台混合开发这种开发模式有如下缺点&#xff1a; 沟通成本高&#xff1a;后台人员发现前端有问题&#xff0…...

【js/es6】合集

目录 随机生成字符串每十个字符换行 随机生成字符串 function generateRandomAlphaNum(len) {var rdmString "";for (; rdmString.length < len; rdmString Math.random().toString(36).substr(2));return rdmString.substr(0, len); }每十个字符换行 string.…...

Nginx modules build fail:field ‘pkt6’ has incomplete type

前言 最近升级Nginx 1.24.0&#xff0c;编译第三方module出错&#xff1a; /usr/src/nginx_modules/ngx_json_post_module/src/ngx_json_post_module.c In file included from src/event/ngx_event.h:526,from src/http/ngx_http_upstream.h:14,from src/http/ngx_http.h:34,…...

电脑突然提示mfc140u.dll丢失,缺失mfc140u.dll无法运行程序的解决方法

在当今信息化社会&#xff0c;电脑已经成为我们生活和工作中不可或缺的一部分。然而&#xff0c;随着技术的不断发展&#xff0c;电脑也会出现各种问题。其中&#xff0c;最常见的问题之一就是“mfc140u.dll丢失”。那么&#xff0c;当我们遇到这个问题时&#xff0c;应该如何解…...

国庆day1

消息队列 代码 发送 #include<myhead.h> //声明一个消息结构体 typedef struct {long msgtype; //消息类型char data[1024]; //消息正文 }Msg_s; #define SIZE sizeof(Msg_s)-sizeof(long) //消息正文的大小 int main(int argc, const char *argv[]) {key_t key; /…...

从零开始之了解电机及其控制(11)实现空间矢量调制

广泛地说&#xff0c;空间矢量调制只是将电压矢量以及磁场矢量在空间中调制到任意角度&#xff0c;通常同时最大限度地利用整个电压范围。 其他空间矢量调制模式确实存在&#xff0c;并且根据您最关心的内容&#xff0c;它们可能值得研究。 如何实际执行这种所谓的交替反向序列…...

如何将html转换成markdown

什么是Turndown “Turndown” 是一个用于将 HTML 转换为 Markdown 的 JavaScript 库。它通常用于将富文本内容从网页或其他 HTML 格式转换为纯文本 Markdown 格式&#xff0c;以便在不同平台上显示或存储。 如果在有node环境的情况下要使用 Turndown&#xff0c;首先需要将它…...

【VUE复习·3】@keyup.xxx 键盘事件触发函数(单按键 or 组合按键触发)

总览 1.keyup.xxx or keydown.xxx 单按键触发 2.组合按键触发 一、keyup.xxx or keydown.xxx 1.用法 在我们使用 keyup.enter 时&#xff0c;那么我们可以这样写&#xff1a; <div><input type"text" placeholder"按下回车键以确定..." keyu…...

IntelliJ IDEA学习总结(3)—— IntelliJ IDEA 常用快捷键(带动图演示)

一、构建/编译 Ctrl + F9:构建项目 该快捷键,等同于菜单【Build】—>【Build Project】 执行该命令后,IntelliJ IDEA 会编译项目中所有类,并将编译结果输出到out目录中。IntelliJ IDEA 支持增量构建,会在上次构建的基础上,仅编译修改的类。 Ctrl + Shift + F9:重新编…...

巨人互动|Facebook海外户Facebook的特点优势

Facebook作为全球最大的社交媒体平台之一&#xff0c;同时也是最受欢迎的社交网站之一&#xff0c;Facebook具有许多独特的特点和优势。本文小编将说一些关于Facebook的特点及优势。 1、全球化 Facebook拥有数十亿的全球用户&#xff0c;覆盖了几乎所有国家和地区。这使得人们…...

(vue3)create-vue 组合式API

优势&#xff1a; 更易维护&#xff1a;组合式api&#xff0c;更好的TS支持 之前是选项式api&#xff0c;现在是组合式&#xff0c;把同功能的api集合式管理 复用功能封装成一整个函数 更快的速度 更小的体积 更优的数据响应式&#xff1a;Proxy create-vue 新的脚手架工…...

【如何看待Unity收费】对标中小公司的待就业者的该如何做

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…...

c++迷宫小游戏

一、总结 一句话总结&#xff1a; 显示&#xff1a;根据map数组输出图像 走动&#xff1a;修改map数组的值&#xff0c;每走一步重新刷新一下图像就好 1、如果走函数用z()&#xff0c;出现输入s会向下走多步的情况&#xff0c;原因是什么&#xff1f; 向下走两层循环ij增加&a…...

需求堆积,如何排序产品优先极

面对堆积的产品需求&#xff0c;到底该如何排序优先极呢&#xff1f; 需求排期的目标 在谈具体的排期方法之前&#xff0c;有必要先探讨一下——合理的需求排期应该达到什么的目标呢&#xff1f;如果站在与项目相关的利益人员的角度来看&#xff0c;至少应该使以下四方面的收…...

Cursor实现用excel数据填充word模版的方法

cursor主页&#xff1a;https://www.cursor.com/ 任务目标&#xff1a;把excel格式的数据里的单元格&#xff0c;按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例&#xff0c;…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统

医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上&#xff0c;开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识&#xff0c;在 vs 2017 平台上&#xff0c;进行 ASP.NET 应用程序和简易网站的开发&#xff1b;初步熟悉开发一…...

通过Wrangler CLI在worker中创建数据库和表

官方使用文档&#xff1a;Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后&#xff0c;会在本地和远程创建数据库&#xff1a; npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库&#xff1a; 现在&#xff0c;您的Cloudfla…...

java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别

UnsatisfiedLinkError 在对接硬件设备中&#xff0c;我们会遇到使用 java 调用 dll文件 的情况&#xff0c;此时大概率出现UnsatisfiedLinkError链接错误&#xff0c;原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用&#xff0c;结果 dll 未实现 JNI 协…...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

今日科技热点速览

&#x1f525; 今日科技热点速览 &#x1f3ae; 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售&#xff0c;主打更强图形性能与沉浸式体验&#xff0c;支持多模态交互&#xff0c;受到全球玩家热捧 。 &#x1f916; 人工智能持续突破 DeepSeek-R1&…...

大学生职业发展与就业创业指导教学评价

这里是引用 作为软工2203/2204班的学生&#xff0c;我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要&#xff0c;而您认真负责的教学态度&#xff0c;让课程的每一部分都充满了实用价值。 尤其让我…...

如何在最短时间内提升打ctf(web)的水平?

刚刚刷完2遍 bugku 的 web 题&#xff0c;前来答题。 每个人对刷题理解是不同&#xff0c;有的人是看了writeup就等于刷了&#xff0c;有的人是收藏了writeup就等于刷了&#xff0c;有的人是跟着writeup做了一遍就等于刷了&#xff0c;还有的人是独立思考做了一遍就等于刷了。…...