活动介绍

/** * @author zhaobingqian * @version 1.0 * @since 2021/12/27 */ @Configuration @EnableWebSocketMessageBroker public class WebsocketConfig extends WebSocketMessageBrokerConfigurationSupport implements WebSocketMessageBrokerConfigurer { @Qualifier(ThreadPoolNameConstant.API_WEBSOCKET_MESSAGE_OUT_POOL) @Autowired private ThreadPoolTaskExecutor threadPoolTaskExecutor; @Resource private HttpSessionInterceptor httpSessionInterceptor; @Resource private CloudAccessHttpSessionInterceptor cloudAccessHttpSessionInterceptor; @Resource private CloudAccessHttpSessionInterceptorV2 cloudAccessHttpSessionInterceptorV2; @Resource private MessageInboundInterceptor messageInboundV2Interceptor; @Resource private MessageOutboundInterceptor messageOutboundInterceptor; /** * 注册Stomp的端点,这个HTTP URL是供WebSocket或SockJS客户端访问的地址 */ @Override public void registerStompEndpoints(StompEndpointRegistry registry) { // normal stomp endpoint registry.addEndpoint(WebsocketConstant.NORMAL_ENDPOINT) .addInterceptors(httpSessionInterceptor) .setAllowedOriginPatterns("*") .withSockJS() // 设置SockJS连接超时时间为30s .setDisconnectDelay(TimeUnit.SECONDS.toMillis(30)); // cloud-access(local vms) stomp endpoint registry.addEndpoint(WebsocketConstant.CLOUD_ACCESS_ENDPOINT) .addInterceptors(cloudAccessHttpSessionInterceptor) .setAllowedOriginPatterns("*") .withSockJS() // 设置SockJS连接超时时间为30s .setDisconnectDelay(TimeUnit.SECONDS.toMillis(30)); // cloud-access(local vms) stomp endpoint registry.addEndpoint(WebsocketConstant.CLOUD_ACCESS_ENDPOINT_V2) .addInterceptors(cloudAccessHttpSessionInterceptorV2) .setAllowedOriginPatterns("*") .withSockJS() // 设置SockJS连接超时时间为30s .setDisconnectDelay(TimeUnit.SECONDS.toMillis(30)); // devops endpoint registry.addEndpoint(WebsocketConstant.DEVOPS_ENDPOINT) .addInterceptors(new DevopsHandshakeInterceptor()) // 添加允许跨域访问 .setAllowedOriginPatterns("*") // 支持SockJS .withSockJS() // 设置SockJS连接超时时间为30s .setDisconnectDelay(TimeUnit.SECONDS.toMillis(30)); } @Override public void configureWebSocketTransport(WebSocketTransportRegistration registry) { } /** * 配置消息代理 */ @Override public void configureMessageBroker(MessageBrokerRegistry registry) { super.configureMessageBroker(registry); // 在 /topic、/queue 这三个域上可以向客户端发消息, 规定 /topic 用于广播,/queue 用于单播 registry.enableSimpleBroker( WebsocketConstant.SIMPLE_BROKER_PREFIX_TOPIC, WebsocketConstant.SIMPLE_BROKER_PREFIX_QUEUE); // 给指定用户发送一对一的消息前缀 /v2user,用于单播 registry.setUserDestinationPrefix(WebsocketConstant.USER_DESTINATION_PREFIX_V2); // 客户端向服务端发起请求时,需要以/app为前缀,可被controller拦截,能进入controller层(未使用) registry.setApplicationDestinationPrefixes(WebsocketConstant.APPLICATION_DESTINATION_PREFIX); } @Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors(messageInboundV2Interceptor); } @Override public void configureClientOutboundChannel(ChannelRegistration registration) { registration.interceptors(messageOutboundInterceptor); // 使用线程池增加WebSocket发送消息的并发性能 registration.taskExecutor(threadPoolTaskExecutor); } @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) { super.addArgumentResolvers(argumentResolvers); } @Override public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) { super.addReturnValueHandlers(returnValueHandlers); } @Override public boolean configureMessageConverters(List<MessageConverter> messageConverters) { if (CollectionUtils.isEmpty(messageConverters)) { messageConverters.add(new StringMessageConverter()); messageConverters.add(new ByteArrayMessageConverter()); messageConverters.add(createJacksonConverter()); } // 返回false后Spring不会添加其他默认Converter return false; } @Bean @Override public WebSocketHandler subProtocolWebSocketHandler(AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel) { return new SessionHolderWsHandler(clientInboundChannel, clientOutboundChannel); } }讲解一下

时间: 2025-09-16 07:45:53 AIGC 浏览: 1
这段代码是一个 Spring WebSocket 的配置类 `WebsocketConfig`,主要用于配置 WebSocket 消息代理、端点注册、消息转换器、拦截器等。以下是对代码的详细讲解: --- ### **1. 类定义** ```java @Configuration @EnableWebSocketMessageBroker public class WebsocketConfig extends WebSocketMessageBrokerConfigurationSupport implements WebSocketMessageBrokerConfigurer { ``` - **`@Configuration`**:标识这是一个 Spring 配置类。 - **`@EnableWebSocketMessageBroker`**:启用 WebSocket 消息代理功能,支持 STOMP 协议。 - **`extends WebSocketMessageBrokerConfigurationSupport`**:继承基础配置类,提供默认的 WebSocket 配置。 - **`implements WebSocketMessageBrokerConfigurer`**:实现接口,用于自定义 WebSocket 配置。 --- ### **2. 依赖注入** ```java @Qualifier(ThreadPoolNameConstant.API_WEBSOCKET_MESSAGE_OUT_POOL) @Autowired private ThreadPoolTaskExecutor threadPoolTaskExecutor; @Resource private HttpSessionInterceptor httpSessionInterceptor; @Resource private CloudAccessHttpSessionInterceptor cloudAccessHttpSessionInterceptor; @Resource private CloudAccessHttpSessionInterceptorV2 cloudAccessHttpSessionInterceptorV2; @Resource private MessageInboundInterceptor messageInboundV2Interceptor; @Resource private MessageOutboundInterceptor messageOutboundInterceptor; ``` - **`ThreadPoolTaskExecutor`**:用于配置 WebSocket 消息发送的线程池,提高并发性能。 - **拦截器(Interceptors)**: - `HttpSessionInterceptor`:普通 HTTP 会话拦截器。 - `CloudAccessHttpSessionInterceptor` 和 `CloudAccessHttpSessionInterceptorV2`:云访问相关的会话拦截器。 - `MessageInboundInterceptor` 和 `MessageOutboundInterceptor`:消息入站和出站拦截器。 --- ### **3. 注册 STOMP 端点** ```java @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint(WebsocketConstant.NORMAL_ENDPOINT) .addInterceptors(httpSessionInterceptor) .setAllowedOriginPatterns("*") .withSockJS() .setDisconnectDelay(TimeUnit.SECONDS.toMillis(30)); registry.addEndpoint(WebsocketConstant.CLOUD_ACCESS_ENDPOINT) .addInterceptors(cloudAccessHttpSessionInterceptor) .setAllowedOriginPatterns("*") .withSockJS() .setDisconnectDelay(TimeUnit.SECONDS.toMillis(30)); registry.addEndpoint(WebsocketConstant.CLOUD_ACCESS_ENDPOINT_V2) .addInterceptors(cloudAccessHttpSessionInterceptorV2) .setAllowedOriginPatterns("*") .withSockJS() .setDisconnectDelay(TimeUnit.SECONDS.toMillis(30)); registry.addEndpoint(WebsocketConstant.DEVOPS_ENDPOINT) .addInterceptors(new DevopsHandshakeInterceptor()) .setAllowedOriginPatterns("*") .withSockJS() .setDisconnectDelay(TimeUnit.SECONDS.toMillis(30)); } ``` - **功能**:注册 WebSocket 或 SockJS 的端点,客户端通过这些端点建立连接。 - **配置项**: - **`addEndpoint()`**:定义 WebSocket 端点路径(如 `/ws`)。 - **`addInterceptors()`**:添加握手拦截器,用于验证连接。 - **`setAllowedOriginPatterns("*")`**:允许跨域请求(`*` 表示允许所有来源)。 - **`withSockJS()`**:启用 SockJS 回退选项(兼容不支持 WebSocket 的浏览器)。 - **`setDisconnectDelay()`**:设置 SockJS 连接断开延迟(30 秒)。 --- ### **4. 配置消息代理** ```java @Override public void configureMessageBroker(MessageBrokerRegistry registry) { super.configureMessageBroker(registry); registry.enableSimpleBroker( WebsocketConstant.SIMPLE_BROKER_PREFIX_TOPIC, WebsocketConstant.SIMPLE_BROKER_PREFIX_QUEUE); registry.setUserDestinationPrefix(WebsocketConstant.USER_DESTINATION_PREFIX_V2); registry.setApplicationDestinationPrefixes(WebsocketConstant.APPLICATION_DESTINATION_PREFIX); } ``` - **功能**:配置消息代理,用于路由消息(广播、单播)。 - **配置项**: - **`enableSimpleBroker()`**:启用简单内存消息代理,支持 `/topic`(广播)和 `/queue`(单播)。 - **`setUserDestinationPrefix()`**:设置单播消息前缀(如 `/v2user`)。 - **`setApplicationDestinationPrefixes()`**:设置客户端发送消息的前缀(如 `/app`),可被 `@Controller` 拦截。 --- ### **5. 配置客户端通道** ```java @Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors(messageInboundV2Interceptor); } @Override public void configureClientOutboundChannel(ChannelRegistration registration) { registration.interceptors(messageOutboundInterceptor); registration.taskExecutor(threadPoolTaskExecutor); } ``` - **`configureClientInboundChannel`**:配置入站通道拦截器(如权限校验)。 - **`configureClientOutboundChannel`**:配置出站通道拦截器,并指定线程池提高并发性能。 --- ### **6. 消息转换器** ```java @Override public boolean configureMessageConverters(List<MessageConverter> messageConverters) { if (CollectionUtils.isEmpty(messageConverters)) { messageConverters.add(new StringMessageConverter()); messageConverters.add(new ByteArrayMessageConverter()); messageConverters.add(createJacksonConverter()); } return false; } ``` - **功能**:配置消息转换器,用于序列化/反序列化消息。 - **转换器类型**: - `StringMessageConverter`:处理字符串消息。 - `ByteArrayMessageConverter`:处理二进制消息。 - `JacksonConverter`:处理 JSON 消息(通过 `createJacksonConverter()` 创建)。 - **`return false`**:表示不使用 Spring 默认的转换器。 --- ### **7. 自定义 WebSocket 处理器** ```java @Bean @Override public WebSocketHandler subProtocolWebSocketHandler( AbstractSubscribableChannel clientInboundChannel, AbstractSubscribableChannel clientOutboundChannel) { return new SessionHolderWsHandler(clientInboundChannel, clientOutboundChannel); } ``` - **功能**:自定义 WebSocket 处理器,继承默认的 `SubProtocolWebSocketHandler`。 - **`SessionHolderWsHandler`**:可能是自定义类,用于管理 WebSocket 会话。 --- ### **总结** 这段代码完成了以下功能: 1. **注册 WebSocket 端点**:支持普通和云访问场景,兼容 SockJS。 2. **配置消息代理**:实现广播(`/topic`)和单播(`/queue`、`/v2user`)。 3. **拦截器**:对消息的入站、出站和握手过程进行拦截。 4. **线程池优化**:提高消息发送的并发性能。 5. **消息转换**:支持字符串、二进制和 JSON 格式。 6. **自定义处理器**:扩展默认的 WebSocket 行为。 ---
阅读全文

相关推荐

package com.tplink.nbu.demo.basicspringboot.service; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserRegisterDTO; /** * @author [email protected] * @version 1.0 * @since 2020/7/13 */ public interface UserService { boolean auth(UserLoginDTO loginDTO); boolean register(UserRegisterDTO registerDTO); } /* * Copyright (c) 2025, TP-Link. All rights reserved. */ package com.tplink.nbu.demo.basicspringboot.repository; import com.tplink.nbu.demo.basicspringboot.bean.UserInfo; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; /** * Description of this file * @author Liu Long * @version 1.0 * @since 2025/8/21 */ @Repository public interface UserInfoRepository extends JpaRepository<UserInfo, Long> { // 根据用户名查询 UserInfo findByUsername(String username); // 根据邮箱查询 UserInfo findByEmail(String email); // 用户名是否已存在 boolean existsByUsername(String username); // 邮箱是否已存在 boolean existsByEmail(String email); } /* * Copyright (c) 2020, TP-Link Co.,Ltd. All rights reserved. */ package com.tplink.nbu.demo.basicspringboot.service; import lombok.AllArgsConstructor; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import io.micrometer.core.instrument.MeterRegistry; import org.springframework.transaction.annotation.Transactional; import com.tplink.nbu.demo.basicspringboot.config.UserRegistrationConfig; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserRegisterDTO; import com.tplink.nbu.demo.basicspringboot.bean.UserInfo; import com.tplink.nbu.demo.basicspringboot.repository.UserInfoRepository; import java.util.Optional; /** * @author [email protected] * @version 1.0 * @since 2020/7/13 */ @Service @AllArgsConstructor public class UserServiceImpl implements UserService { private final MeterRegistry meterRegistry; private final UserInfoRepository userInfoRepository; private final UserRegistrationConfig registrationConfig; private final BCryptPasswordEncoder passwordEncoder; @Override public boolean auth(UserLoginDTO loginDTO) { String usernameOrEmail = loginDTO.getUsernameOrEmail(); UserInfo user = userInfoRepository.findByUsername(usernameOrEmail); // 如果没找到用户名,查询是否为邮箱 if (user == null) { user = userInfoRepository.findByEmail(usernameOrEmail); } return user != null && passwordEncoder.matches(loginDTO.getPassword(), user.getPassword()); } @Override @Transactional public boolean register(UserRegisterDTO registerDTO) { if (userInfoRepository.count() >= registrationConfig.getMaxUsers()) { throw new IllegalStateException("Registration failed: Maximum number of users reached" + registrationConfig.getMaxUsers()); } // 用户名是否存在 if (userInfoRepository.existsByUsername(registerDTO.getUsername())) { return false; } // 邮箱是否重复 if(userInfoRepository.existsByEmail(registerDTO.getEmail())) { meterRegistry.counter("user.registration.email.duplicate").increment(); return false; } // 注册新用户信息 UserInfo userInfo = new UserInfo(); userInfo.setUsername(registerDTO.getUsername()); userInfo.setEmail(registerDTO.getEmail()); userInfo.setPassword(passwordEncoder.encode(registerDTO.getPassword())); userInfo.setAddress(registerDTO.getAddress()); userInfoRepository.save(userInfo); return true; } } 现在怎么样,还有错吗。idea中提示无法自动装配,找不到BCryptPasswordEncoder类型的bean怎么解决

我有一个springboot项目,编写了一个简单的账号系统,有登录、登出和注册功能,包括annotation、aspect、bean、config、controller、dto、exception、repository、service文件夹,具体接口controller内容如下: /* Copyright © 2020, TP-Link Co.,Ltd. All rights reserved. */ package com.tplink.nbu.demo.basicspringboot.controller; import javax.validation.Valid; import com.tplink.nbu.demo.basicspringboot.dto.UserRegisterDTO; import com.tplink.nbu.demo.basicspringboot.exception.RepeatRegisterException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.tplink.nbu.demo.basicspringboot.annotation.UserAuth; import com.tplink.nbu.demo.basicspringboot.bean.UserInfo; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginSuccessDTO; import com.tplink.nbu.demo.basicspringboot.exception.UnauthorizedException; import com.tplink.nbu.demo.basicspringboot.service.UserService; /** @author [email protected] @version 1.0 @since 2020/7/13 */ @Slf4j @RestController @RequestMapping(“/user”) public class UserLogInOutController { @Autowired private UserService userService; /** 注册用户 @param registerDTO 注册信息 @return */ @PostMapping(“/register”) public ResponseEntity register(@Valid @RequestBody UserRegisterDTO registerDTO) { boolean success = userService.addUser(registerDTO); if (!success) { throw new RepeatRegisterException(); } log.info(“{} register”, registerDTO.getUsername()); return ResponseEntity.ok(registerDTO); } /** 用户登录 @param loginDTO 登录用户名/邮箱+密码 @return */ @PostMapping(“/login”) public UserLoginSuccessDTO login(@Valid @RequestBody UserLoginDTO loginDTO) { boolean auth = userService.auth(loginDTO); if (!auth) { throw new UnauthorizedException(); } log.info(“{} login”, loginDTO.getUsername()); return UserLoginSuccessDTO.builder() .token(loginDTO.getUsername()) .build(); } /** 用户登出 @param userInfo 用户信息 @return */ @UserAuth @PostMapping(“/logout”) public UserInfo logout(UserInfo userInfo) { log.info(“{} logout”, userInfo.getUsername()); return userInfo; } } service具体实现如下 import com.tplink.nbu.demo.basicspringboot.bean.UserInfo; import com.tplink.nbu.demo.basicspringboot.config.RegisterNumberConfig; import com.tplink.nbu.demo.basicspringboot.dto.UserRegisterDTO; import com.tplink.nbu.demo.basicspringboot.repository.UserRepository; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginDTO; /** * @author [email protected] * @version 1.0 * @since 2020/7/13 */ @Service public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Autowired private RegisterNumberConfig registerNumberConfig; /** * 添加Metrics注册 */ private final Counter emailDuplicateCounter; public UserServiceImpl(MeterRegistry meterRegistry) { this.emailDuplicateCounter = meterRegistry.counter("user.register.email.duplicate.count"); } /** * 判断用户身份 * * @param loginDTO 登录信息 * @return */ @Override public boolean auth(UserLoginDTO loginDTO) { return userRepository.findByUsernameAndPassword(loginDTO.getUsername(), loginDTO.getPassword()).isPresent() || userRepository.findByEmailAndPassword(loginDTO.getUsername(), loginDTO.getPassword()).isPresent(); } /** * 新增用户,用户名和邮箱不允许重复 * * @param registerDTO 注册信息 * @return */ @Override public boolean addUser(UserRegisterDTO registerDTO) { if (userRepository.count() >= registerNumberConfig.getMaxUsers()) { throw new RuntimeException("系统用户数已达上限,无法注册新用户"); } if (userRepository.findByEmail(registerDTO.getEmail()).isPresent()) { emailDuplicateCounter.increment(); } if (userRepository.findByEmail(registerDTO.getEmail()).isPresent() || userRepository.findByUsername(registerDTO.getUsername()).isPresent()) { return false; } userRepository.save(new UserInfo(registerDTO.getUsername(), registerDTO.getPassword(), registerDTO.getEmail(), registerDTO.getAddress())); return true; } } 现在需要基于 gRPC 实现原有的 HTTP 接口; 并且实现一个 gRPC 客户端,用于发送请求和接收响应; 服务调用方式为异步非阻塞(基于 Future-Listener);

我现在通过Spring Data JPA使用mysql来实现我项目的数据管理,相关代码如下package com.tplink.nbu.demo.basicspringboot.service; import com.tplink.nbu.demo.basicspringboot.bean.UserInfo; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserRegisterDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserUpdateDTO; import com.tplink.nbu.demo.basicspringboot.entity.User; /** @author [email protected] @version 1.0 @since 2020/7/13 */ public interface UserService { // 登陆验证 boolean auth(UserLoginDTO loginDTO); // 注册方法 void register(UserRegisterDTO registerDTO); // 统计用户总数 long count(); // 邮箱存在校验 boolean existsByEmail(String email); // 注销方法 void delete(String username); // 查询方法 User getUser(String usernameOrEmail); // 修改方法 void updateUser(UserUpdateDTO updateDTO, UserInfo userInfo); } /* Copyright © 2020, TP-Link Co.,Ltd. All rights reserved. */ package com.tplink.nbu.demo.basicspringboot.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.tplink.nbu.demo.basicspringboot.bean.UserInfo; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserRegisterDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserUpdateDTO; import com.tplink.nbu.demo.basicspringboot.entity.User; import com.tplink.nbu.demo.basicspringboot.repository.UserRepository; /** @author [email protected] @version 1.0 @since 2020/7/13 */ @Service public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Override public boolean auth(UserLoginDTO loginDTO) { // 通过用户名查询用户 User user = userRepository.findByUsername(loginDTO.getUsernameOrEmail()); // 若用户名查不到,再通过邮箱查询 if (user == null) { user = userRepository.findByEmail(loginDTO.getUsernameOrEmail()); } return user != null && user.getPassword().equals(loginDTO.getPassword()); } // 注册逻辑 @Override public void register(UserRegisterDTO registerDTO) { User user = new User(); user.setUsername(registerDTO.getUsername()); user.setPassword(registerDTO.getPassword()); user.setEmail(registerDTO.getEmail()); user.setAddress(registerDTO.getAddress()); userRepository.save(user); } // 统计用户总数 @Override public long count() { return userRepository.count(); } // 校验邮箱是否已存在 @Override public boolean existsByEmail(String email) { return userRepository.existsByEmail(email); } // 注销逻辑 @Override public void delete(String username) { User user = userRepository.findByUsername(username); userRepository.delete(user); } // 查询逻辑 @Override public User getUser(String username) { return userRepository.findByUsername(username); } // 更新逻辑 @Override public void updateUser(UserUpdateDTO updateDTO, UserInfo userInfo) { User existingUser = userRepository.findByUsername(userInfo.getUsername()); if (updateDTO.getNewPassword() != null) { existingUser.setPassword(updateDTO.getNewPassword()); } if (updateDTO.getNewAddress() != null) { existingUser.setAddress(updateDTO.getNewAddress()); } userRepository.save(existingUser); } } package com.tplink.nbu.demo.basicspringboot.entity; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import javax.persistence.*; /** @author Liu Long @version 1.0 @since 2025/8/22 */ @Entity @Table(name = “user”, uniqueConstraints = {@UniqueConstraint(columnNames = “email”)}) @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; private String email; private String address; } package com.tplink.nbu.demo.basicspringboot.repository; import com.tplink.nbu.demo.basicspringboot.entity.User; import org.springframework.data.jpa.repository.JpaRepository; /** @author Liu Long @version 1.0 @since 2025/8/22 */ public interface UserRepository extends JpaRepository<User, Long> { // 根据用户名查询用户 User findByUsername(String username); // 根据邮箱查询用户 User findByEmail(String email); // 校验邮箱是否存在 boolean existsByEmail(String email); } MySQL spring.datasource.url=jdbc:mysql://localhost:3306/user_db?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver我现在有个想法,使用Spring Data for Apache Cassandra来通过Cassandra实现user的数据存储,达到可替换MySQL,并且在Controller层不变的情况下,实现通过简单修改配置后,更换不同的数据源,如何做到这一点,给我详细的教程,我已经装了cassandra,我的配置文件是application.properties

我现在实现了一个springboot项目,用户系统,用mysql实现持久化。我现在想利用cassandra实现持久化,使用Spring Data for Apache Cassandra实现user,并且与mysql同时存在,在Controller层不变的情况下,实现通过简单修改配置后,更换不同的数据库。我的controller代码是/* * Copyright (c) 2020, TP-Link Co.,Ltd. All rights reserved. */ package com.tplink.nbu.demo.basicspringboot.controller; import javax.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.tplink.nbu.demo.basicspringboot.annotation.UserAuth; import com.tplink.nbu.demo.basicspringboot.bean.UserInfo; import com.tplink.nbu.demo.basicspringboot.component.EmailDuplicateCounter; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginSuccessDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserRegisterDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserUpdateDTO; import com.tplink.nbu.demo.basicspringboot.entity.User; import com.tplink.nbu.demo.basicspringboot.exception.EmailExistsException; import com.tplink.nbu.demo.basicspringboot.exception.MaxRegisterExceededException; import com.tplink.nbu.demo.basicspringboot.exception.UnauthorizedException; import com.tplink.nbu.demo.basicspringboot.service.UserService; /** * @author [email protected] * @version 1.0 * @since 2020/7/13 */ @Slf4j @RestController @RequestMapping("/user") public class UserLogInOutController { @Autowired private UserService userService; // 注入最大注册数配置 @Value("${system.user.max-register}") private int maxRegister; @Autowired private EmailDuplicateCounter emailDuplicateCounter; // 登录接口 @PostMapping("/login") public UserLoginSuccessDTO login(@Valid @RequestBody UserLoginDTO loginDTO) { boolean auth = userService.auth(loginDTO); if (!auth) { throw new UnauthorizedException(); } log.info("{} login", loginDTO.getUsernameOrEmail()); return UserLoginSuccessDTO.builder() .token(loginDTO.getUsernameOrEmail()) .build(); } // 登出接口 @UserAuth @PostMapping("/logout") public UserInfo logout(UserInfo userInfo) { log.info("{} logout", userInfo.getUsername()); return userInfo; } // 注册接口 @PostMapping("/register") public String register(@Valid @RequestBody UserRegisterDTO registerDTO) { // 邮箱是否重复 if (userService.existsByEmail(registerDTO.getEmail())) { // 触发metrics统计 emailDuplicateCounter.increment(); throw new EmailExistsException(); } // 是否超过最大注册数 if (userService.count() >= maxRegister) { throw new MaxRegisterExceededException(); } // 注册 userService.register(registerDTO); return "REGISTERED SUCCESSFULLY"; } // 注销接口 @UserAuth @PostMapping("/delete") public String delete(UserInfo userInfo) { userService.delete(userInfo.getUsername()); return userInfo.getUsername() + " 注销成功"; } // 查询信息 @UserAuth @PostMapping("/info") public User getUserInfo(UserInfo userInfo) { return userService.getUser(userInfo.getUsername()); } // 修改信息 @UserAuth @PostMapping("/update") public String updateUserInfo(@Valid @RequestBody UserUpdateDTO updateDTO, UserInfo userInfo) { userService.updateUser(updateDTO, userInfo); return userInfo.getUsername() + "用户信息更新成功"; } } mysql数据实体类是package com.tplink.nbu.demo.basicspringboot.entity; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import javax.persistence.*; /** * @author Liu Long * @version 1.0 * @since 2025/8/22 */ @Entity @Table(name = "user", uniqueConstraints = {@UniqueConstraint(columnNames = "email")}) @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; private String email; private String address; } mysql的repository是package com.tplink.nbu.demo.basicspringboot.repository; import com.tplink.nbu.demo.basicspringboot.entity.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; /** * @author Liu Long * @version 1.0 * @since 2025/8/22 */ @Repository public interface UserRepository extends JpaRepository<User, Long> { // 根据用户名查询用户 User findByUsername(String username); // 根据邮箱查询用户 User findByEmail(String email); // 校验邮箱是否存在 boolean existsByEmail(String email); } service接口是package com.tplink.nbu.demo.basicspringboot.service; import com.tplink.nbu.demo.basicspringboot.bean.UserInfo; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserRegisterDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserUpdateDTO; import com.tplink.nbu.demo.basicspringboot.entity.User; /** * @author [email protected] * @version 1.0 * @since 2020/7/13 */ public interface UserService { // 登陆验证 boolean auth(UserLoginDTO loginDTO); // 注册方法 void register(UserRegisterDTO registerDTO); // 统计用户总数 long count(); // 邮箱存在校验 boolean existsByEmail(String email); // 注销方法 void delete(String username); // 查询方法 User getUser(String usernameOrEmail); // 修改方法 void updateUser(UserUpdateDTO updateDTO, UserInfo userInfo); } service实现类是/* * Copyright (c) 2020, TP-Link Co.,Ltd. All rights reserved. */ package com.tplink.nbu.demo.basicspringboot.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.tplink.nbu.demo.basicspringboot.bean.UserInfo; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserRegisterDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserUpdateDTO; import com.tplink.nbu.demo.basicspringboot.entity.User; import com.tplink.nbu.demo.basicspringboot.repository.UserRepository; /** * @author [email protected] * @version 1.0 * @since 2020/7/13 */ @Service public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Override public boolean auth(UserLoginDTO loginDTO) { // 通过用户名查询用户 User user = userRepository.findByUsername(loginDTO.getUsernameOrEmail()); // 若用户名查不到,再通过邮箱查询 if (user == null) { user = userRepository.findByEmail(loginDTO.getUsernameOrEmail()); } return user != null && user.getPassword().equals(loginDTO.getPassword()); } // 注册逻辑 @Override public void register(UserRegisterDTO registerDTO) { User user = new User(); user.setUsername(registerDTO.getUsername()); user.setPassword(registerDTO.getPassword()); user.setEmail(registerDTO.getEmail()); user.setAddress(registerDTO.getAddress()); userRepository.save(user); } // 统计用户总数 @Override public long count() { return userRepository.count(); } // 校验邮箱是否已存在 @Override public boolean existsByEmail(String email) { return userRepository.existsByEmail(email); } // 注销逻辑 @Override public void delete(String username) { User user = userRepository.findByUsername(username); userRepository.delete(user); } // 查询逻辑 @Override public User getUser(String username) { return userRepository.findByUsername(username); } // 更新逻辑 @Override public void updateUser(UserUpdateDTO updateDTO, UserInfo userInfo) { User existingUser = userRepository.findByUsername(userInfo.getUsername()); if (updateDTO.getNewPassword() != null) { existingUser.setPassword(updateDTO.getNewPassword()); } if (updateDTO.getNewAddress() != null) { existingUser.setAddress(updateDTO.getNewAddress()); } userRepository.save(existingUser); } } 配置文件application.properties是# MySQL spring.datasource.url=jdbc:mysql://localhost:3306/user_db?createDatabaseIfNotExist=true&useSSL=false&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # Cassandra #spring.data.cassandra.contact-points=localhost #spring.data.cassandra.port=9042 #spring.data.cassandra.keyspace-name=user_keyspace #spring.data.cassandra.local-datacenter=datacenter1 #spring.data.cassandra.schema-action=CREATE_IF_NOT_EXISTS # JPA spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect # Actuator management.endpoints.web.exposure.include=metrics,prometheus # max register system.user.max-register=100我该怎么修改,给我详细的步骤和过程,谢谢

/* * Copyright (c) 2020, TP-Link Co.,Ltd. All rights reserved. */ package com.tplink.nbu.demo.basicspringboot.controller; import javax.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.tplink.nbu.demo.basicspringboot.annotation.UserAuth; import com.tplink.nbu.demo.basicspringboot.bean.UserInfo; import com.tplink.nbu.demo.basicspringboot.component.EmailDuplicateCounter; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginSuccessDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserRegisterDTO; import com.tplink.nbu.demo.basicspringboot.exception.EmailExistsException; import com.tplink.nbu.demo.basicspringboot.exception.MaxRegisterExceededException; import com.tplink.nbu.demo.basicspringboot.exception.UnauthorizedException; import com.tplink.nbu.demo.basicspringboot.service.UserService; /** * @author [email protected] * @version 1.0 * @since 2020/7/13 */ @Slf4j @RestController @RequestMapping("/user") public class UserLogInOutController { @Autowired private UserService userService; // 注入最大注册数配置 @Value("${system.user.max-register}") private int maxRegister; @Autowired private EmailDuplicateCounter emailDuplicateCounter; // 登录接口 @PostMapping("/login") public UserLoginSuccessDTO login(@Valid @RequestBody UserLoginDTO loginDTO) { boolean auth = userService.auth(loginDTO); if (!auth) { throw new UnauthorizedException(); } log.info("{} login", loginDTO.getUsernameOrEmail()); return UserLoginSuccessDTO.builder() .token(loginDTO.getUsernameOrEmail()) .build(); } // 新增注册接口 @PostMapping("/register") public String register(@Valid @RequestBody UserRegisterDTO registerDTO) { // 邮箱是否重复 if (userService.existsByEmail(registerDTO.getEmail())) { // 触发metrics统计 emailDuplicateCounter.increment(); throw new EmailExistsException(); } // 是否超过最大注册数 if (userService.count() >= maxRegister) { throw new MaxRegisterExceededException(); } // 注册 userService.register(registerDTO); return "REGISTERED SUCCESSFULLY"; } // 登出接口 @UserAuth @PostMapping("/logout") public UserInfo logout(UserInfo userInfo) { log.info("{} logout", userInfo.getUsername()); return userInfo; } } /* * Copyright (c) 2020, TP-Link Co.,Ltd. All rights reserved. */ package com.tplink.nbu.demo.basicspringboot.aspect; import javax.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import com.tplink.nbu.demo.basicspringboot.bean.UserInfo; import com.tplink.nbu.demo.basicspringboot.exception.UnauthorizedException; /** * @author [email protected] * @version 1.0 * @since 2020/7/13 */ @Slf4j @Aspect @Component public class UserAuthAspect { @Pointcut("execution(public * com.tplink.nbu.demo.basicspringboot.controller.*.*(..))") public void controllers() { // controller pointcut definition } @Pointcut("@annotation(com.tplink.nbu.demo.basicspringboot.annotation.UserAuth)") public void needUserAuth() { // need user auth pointcut definition } @Around("controllers() && needUserAuth()") public Object arround(ProceedingJoinPoint pjp) throws Throwable { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder .getRequestAttributes(); HttpServletRequest request = requestAttributes.getRequest(); String credential = this.getCredential(request); if (credential == null) { throw new UnauthorizedException(); } Object[] args = pjp.getArgs(); Object[] newArgs = new Object[args.length]; for (int i = 0; i < args.length; i++) { newArgs[i] = this.checkAndAssignUserInfo(args[i], new UserInfo(credential)); } return pjp.proceed(newArgs); } private Object checkAndAssignUserInfo(Object newArg, UserInfo userInfo) { if (newArg instanceof UserInfo) { return userInfo; } return newArg; } private String getCredential(HttpServletRequest request) { return request.getHeader(HttpHeaders.AUTHORIZATION); } } /* * Copyright (c) 2020, TP-Link Co.,Ltd. All rights reserved. */ package com.tplink.nbu.demo.basicspringboot.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserRegisterDTO; import com.tplink.nbu.demo.basicspringboot.entity.User; import com.tplink.nbu.demo.basicspringboot.repository.UserRepository; /** * @author [email protected] * @version 1.0 * @since 2020/7/13 */ @Service public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Override public boolean auth(UserLoginDTO loginDTO) { // 通过用户名查询用户 User user = userRepository.findByUsername(loginDTO.getUsernameOrEmail()); // 若用户名查不到,再通过邮箱查询 if (user == null) { user = userRepository.findByEmail(loginDTO.getUsernameOrEmail()); } return user != null && user.getPassword().equals(loginDTO.getPassword()); } // 注册逻辑 @Override public void register(UserRegisterDTO registerDTO) { User user = new User(); user.setUsername(registerDTO.getUsername()); user.setPassword(registerDTO.getPassword()); user.setEmail(registerDTO.getEmail()); user.setAddress(registerDTO.getAddress()); userRepository.save(user); } // 统计用户总数 @Override public long count() { return userRepository.count(); } // 校验邮箱是否已存在 @Override public boolean existsByEmail(String email) { return userRepository.existsByEmail(email); } } 看好了,这是我一个项目里的一些核心代码,我想基于grpc实现原有的controller里的三个http接口,然后实现一个grpc客户端,用于发送请求和接收相应,并且服务调用方式为异步非阻塞(基于future-listener)。注意!仔细看好我现有的代码和思路,在构建gepc请求与响应的时候要与http的一致,不要无中生有!有什么问题都以现有的代码为标准!注意代码包的结构,告诉我详细的步骤,每一步该干什么写什么,文件生成在哪,最后如何检验功能是否完好,如何启动 log.info This domain may be for sale!

/* * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.scheduling.quartz; import java.io.IOException; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import javax.sql.DataSource; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.quartz.SchedulerFactory; import org.quartz.impl.RemoteScheduler; import org.quartz.impl.SchedulerRepository; import org.quartz.impl.StdSchedulerFactory; import org.quartz.simpl.SimpleThreadPool; import org.quartz.spi.JobFactory; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.SmartLifecycle; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.lang.Nullable; import org.springframework.scheduling.SchedulingException; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; /** * {@link FactoryBean} that creates and configures a Quartz {@link org.quartz.Scheduler}, * manages its lifecycle as part of the Spring application context, and exposes the * Scheduler as bean reference for dependency injection. * * Allows registration of JobDetails, Calendars and Triggers, automatically * starting the scheduler on initialization and shutting it down on destruction. * In scenarios that just require static registration of jobs at startup, there * is no need to access the Scheduler instance itself in application code. * * For dynamic registration of jobs at runtime, use a bean reference to * this SchedulerFactoryBean to get direct access to the Quartz Scheduler * ({@code org.quartz.Scheduler}). This allows you to create new jobs * and triggers, and also to control and monitor the entire Scheduler. * * Note that Quartz instantiates a new Job for each execution, in * contrast to Timer which uses a TimerTask instance that is shared * between repeated executions. Just JobDetail descriptors are shared. * * When using persistent jobs, it is strongly recommended to perform all * operations on the Scheduler within Spring-managed (or plain JTA) transactions. * Else, database locking will not properly work and might even break. * (See {@link #setDataSource setDataSource} javadoc for details.) * * The preferred way to achieve transactional execution is to demarcate * declarative transactions at the business facade level, which will * automatically apply to Scheduler operations performed within those scopes. * Alternatively, you may add transactional advice for the Scheduler itself. * * Compatible with Quartz 2.1.4 and higher, as of Spring 4.1. * * @author Juergen Hoeller * @since 18.02.2004 * @see #setDataSource * @see org.quartz.Scheduler * @see org.quartz.SchedulerFactory * @see org.quartz.impl.StdSchedulerFactory * @see org.springframework.transaction.interceptor.TransactionProxyFactoryBean */ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBean<Scheduler>, BeanNameAware, ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle { /** * The thread count property. */ public static final String PROP_THREAD_COUNT = "org.quartz.threadPool.threadCount"; /** * The default thread count. */ public static final int DEFAULT_THREAD_COUNT = 10; private static final ThreadLocal<ResourceLoader> configTimeResourceLoaderHolder = new ThreadLocal<>(); private static final ThreadLocal<Executor> configTimeTaskExecutorHolder = new ThreadLocal<>(); private static final ThreadLocal<DataSource> configTimeDataSourceHolder = new ThreadLocal<>(); private static final ThreadLocal<DataSource> configTimeNonTransactionalDataSourceHolder = new ThreadLocal<>(); /** * Return the {@link ResourceLoader} for the currently configured Quartz Scheduler, * to be used by {@link ResourceLoaderClassLoadHelper}. * This instance will be set before initialization of the corresponding Scheduler, * and reset immediately afterwards. It is thus only available during configuration. * @see #setApplicationContext * @see ResourceLoaderClassLoadHelper */ @Nullable public static ResourceLoader getConfigTimeResourceLoader() { return configTimeResourceLoaderHolder.get(); } /** * Return the {@link Executor} for the currently configured Quartz Scheduler, * to be used by {@link LocalTaskExecutorThreadPool}. * This instance will be set before initialization of the corresponding Scheduler, * and reset immediately afterwards. It is thus only available during configuration. * @since 2.0 * @see #setTaskExecutor * @see LocalTaskExecutorThreadPool */ @Nullable public static Executor getConfigTimeTaskExecutor() { return configTimeTaskExecutorHolder.get(); } /** * Return the {@link DataSource} for the currently configured Quartz Scheduler, * to be used by {@link LocalDataSourceJobStore}. * This instance will be set before initialization of the corresponding Scheduler, * and reset immediately afterwards. It is thus only available during configuration. * @since 1.1 * @see #setDataSource * @see LocalDataSourceJobStore */ @Nullable public static DataSource getConfigTimeDataSource() { return configTimeDataSourceHolder.get(); } /** * Return the non-transactional {@link DataSource} for the currently configured * Quartz Scheduler, to be used by {@link LocalDataSourceJobStore}. * This instance will be set before initialization of the corresponding Scheduler, * and reset immediately afterwards. It is thus only available during configuration. * @since 1.1 * @see #setNonTransactionalDataSource * @see LocalDataSourceJobStore */ @Nullable public static DataSource getConfigTimeNonTransactionalDataSource() { return configTimeNonTransactionalDataSourceHolder.get(); } @Nullable private SchedulerFactory schedulerFactory; private Class<? extends SchedulerFactory> schedulerFactoryClass = StdSchedulerFactory.class; @Nullable private String schedulerName; @Nullable private Resource configLocation; @Nullable private Properties quartzProperties; @Nullable private Executor taskExecutor; @Nullable private DataSource dataSource; @Nullable private DataSource nonTransactionalDataSource; @Nullable private Map<String, ?> schedulerContextMap; @Nullable private String applicationContextSchedulerContextKey; @Nullable private JobFactory jobFactory; private boolean jobFactorySet = false; private boolean autoStartup = true; private int startupDelay = 0; private int phase = DEFAULT_PHASE; private boolean exposeSchedulerInRepository = false; private boolean waitForJobsToCompleteOnShutdown = false; @Nullable private String beanName; @Nullable private ApplicationContext applicationContext; @Nullable private Scheduler scheduler; /** * Set an external Quartz {@link SchedulerFactory} instance to use. * Default is an internal {@link StdSchedulerFactory} instance. If this method is * called, it overrides any class specified through {@link #setSchedulerFactoryClass} * as well as any settings specified through {@link #setConfigLocation}, * {@link #setQuartzProperties}, {@link #setTaskExecutor} or {@link #setDataSource}. * NOTE: With an externally provided {@code SchedulerFactory} instance, * local settings such as {@link #setConfigLocation} or {@link #setQuartzProperties} * will be ignored here in {@code SchedulerFactoryBean}, expecting the external * {@code SchedulerFactory} instance to get initialized on its own. * @since 4.3.15 * @see #setSchedulerFactoryClass */ public void setSchedulerFactory(SchedulerFactory schedulerFactory) { this.schedulerFactory = schedulerFactory; } /** * Set the Quartz {@link SchedulerFactory} implementation to use. * Default is the {@link StdSchedulerFactory} class, reading in the standard * {@code quartz.properties} from {@code quartz.jar}. For applying custom Quartz * properties, specify {@link #setConfigLocation "configLocation"} and/or * {@link #setQuartzProperties "quartzProperties"} etc on this local * {@code SchedulerFactoryBean} instance. * @see org.quartz.impl.StdSchedulerFactory * @see #setConfigLocation * @see #setQuartzProperties * @see #setTaskExecutor * @see #setDataSource */ public void setSchedulerFactoryClass(Class<? extends SchedulerFactory> schedulerFactoryClass) { this.schedulerFactoryClass = schedulerFactoryClass; } /** * Set the name of the Scheduler to create via the SchedulerFactory, as an * alternative to the {@code org.quartz.scheduler.instanceName} property. * If not specified, the name will be taken from Quartz properties * ({@code org.quartz.scheduler.instanceName}), or from the declared * {@code SchedulerFactoryBean} bean name as a fallback. * @see #setBeanName * @see StdSchedulerFactory#PROP_SCHED_INSTANCE_NAME * @see org.quartz.SchedulerFactory#getScheduler() * @see org.quartz.SchedulerFactory#getScheduler(String) */ public void setSchedulerName(String schedulerName) { this.schedulerName = schedulerName; } /** * Set the location of the Quartz properties config file, for example * as classpath resource "classpath:quartz.properties". * Note: Can be omitted when all necessary properties are specified * locally via this bean, or when relying on Quartz' default configuration. * @see #setQuartzProperties */ public void setConfigLocation(Resource configLocation) { this.configLocation = configLocation; } /** * Set Quartz properties, like "org.quartz.threadPool.class". * Can be used to override values in a Quartz properties config file, * or to specify all necessary properties locally. * @see #setConfigLocation */ public void setQuartzProperties(Properties quartzProperties) { this.quartzProperties = quartzProperties; } /** * Set a Spring-managed {@link Executor} to use as Quartz backend. * Exposed as thread pool through the Quartz SPI. * Can be used to assign a local JDK ThreadPoolExecutor or a CommonJ * WorkManager as Quartz backend, to avoid Quartz's manual thread creation. * By default, a Quartz SimpleThreadPool will be used, configured through * the corresponding Quartz properties. * @since 2.0 * @see #setQuartzProperties * @see LocalTaskExecutorThreadPool * @see org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor * @see org.springframework.scheduling.concurrent.DefaultManagedTaskExecutor */ public void setTaskExecutor(Executor taskExecutor) { this.taskExecutor = taskExecutor; } /** * Set the default {@link DataSource} to be used by the Scheduler. * Note: If this is set, the Quartz settings should not define * a job store "dataSource" to avoid meaningless double configuration. * Also, do not define a "org.quartz.jobStore.class" property at all. * (You may explicitly define Spring's {@link LocalDataSourceJobStore} * but that's the default when using this method anyway.) * A Spring-specific subclass of Quartz' JobStoreCMT will be used. * It is therefore strongly recommended to perform all operations on * the Scheduler within Spring-managed (or plain JTA) transactions. * Else, database locking will not properly work and might even break * (for example, if trying to obtain a lock on Oracle without a transaction). * Supports both transactional and non-transactional DataSource access. * With a non-XA DataSource and local Spring transactions, a single DataSource * argument is sufficient. In case of an XA DataSource and global JTA transactions, * SchedulerFactoryBean's "nonTransactionalDataSource" property should be set, * passing in a non-XA DataSource that will not participate in global transactions. * @since 1.1 * @see #setNonTransactionalDataSource * @see #setQuartzProperties * @see #setTransactionManager * @see LocalDataSourceJobStore */ public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * Set the {@link DataSource} to be used for non-transactional access. * This is only necessary if the default DataSource is an XA DataSource that will * always participate in transactions: A non-XA version of that DataSource should * be specified as "nonTransactionalDataSource" in such a scenario. * This is not relevant with a local DataSource instance and Spring transactions. * Specifying a single default DataSource as "dataSource" is sufficient there. * @since 1.1 * @see #setDataSource * @see LocalDataSourceJobStore */ public void setNonTransactionalDataSource(DataSource nonTransactionalDataSource) { this.nonTransactionalDataSource = nonTransactionalDataSource; } /** * Register objects in the Scheduler context via a given Map. * These objects will be available to any Job that runs in this Scheduler. * Note: When using persistent Jobs whose JobDetail will be kept in the * database, do not put Spring-managed beans or an ApplicationContext * reference into the JobDataMap but rather into the SchedulerContext. * @param schedulerContextAsMap a Map with String keys and any objects as * values (for example Spring-managed beans) * @see JobDetailFactoryBean#setJobDataAsMap */ public void setSchedulerContextAsMap(Map<String, ?> schedulerContextAsMap) { this.schedulerContextMap = schedulerContextAsMap; } /** * Set the key of an {@link ApplicationContext} reference to expose in the * SchedulerContext, for example "applicationContext". Default is none. * Only applicable when running in a Spring ApplicationContext. * Note: When using persistent Jobs whose JobDetail will be kept in the * database, do not put an ApplicationContext reference into the JobDataMap * but rather into the SchedulerContext. * In case of a QuartzJobBean, the reference will be applied to the Job * instance as bean property. An "applicationContext" attribute will * correspond to a "setApplicationContext" method in that scenario. * Note that BeanFactory callback interfaces like ApplicationContextAware * are not automatically applied to Quartz Job instances, because Quartz * itself is responsible for the lifecycle of its Jobs. * @see JobDetailFactoryBean#setApplicationContextJobDataKey * @see org.springframework.context.ApplicationContext */ public void setApplicationContextSchedulerContextKey(String applicationContextSchedulerContextKey) { this.applicationContextSchedulerContextKey = applicationContextSchedulerContextKey; } /** * Set the Quartz {@link JobFactory} to use for this Scheduler. * Default is Spring's {@link AdaptableJobFactory}, which supports * {@link java.lang.Runnable} objects as well as standard Quartz * {@link org.quartz.Job} instances. Note that this default only applies * to a local Scheduler, not to a RemoteScheduler (where setting * a custom JobFactory is not supported by Quartz). * Specify an instance of Spring's {@link SpringBeanJobFactory} here * (typically as an inner bean definition) to automatically populate a job's * bean properties from the specified job data map and scheduler context. * @since 2.0 * @see AdaptableJobFactory * @see SpringBeanJobFactory */ public void setJobFactory(JobFactory jobFactory) { this.jobFactory = jobFactory; this.jobFactorySet = true; } /** * Set whether to automatically start the scheduler after initialization. * Default is "true"; set this to "false" to allow for manual startup. */ public void setAutoStartup(boolean autoStartup) { this.autoStartup = autoStartup; } /** * Return whether this scheduler is configured for auto-startup. If "true", * the scheduler will start after the context is refreshed and after the * start delay, if any. */ @Override public boolean isAutoStartup() { return this.autoStartup; } /** * Specify the phase in which this scheduler should be started and stopped. * The startup order proceeds from lowest to highest, and the shutdown order * is the reverse of that. By default this value is {@code Integer.MAX_VALUE} * meaning that this scheduler starts as late as possible and stops as soon * as possible. * @since 3.0 */ public void setPhase(int phase) { this.phase = phase; } /** * Return the phase in which this scheduler will be started and stopped. */ @Override public int getPhase() { return this.phase; } /** * Set the number of seconds to wait after initialization before * starting the scheduler asynchronously. Default is 0, meaning * immediate synchronous startup on initialization of this bean. * Setting this to 10 or 20 seconds makes sense if no jobs * should be run before the entire application has started up. */ public void setStartupDelay(int startupDelay) { this.startupDelay = startupDelay; } /** * Set whether to expose the Spring-managed {@link Scheduler} instance in the * Quartz {@link SchedulerRepository}. Default is "false", since the Spring-managed * Scheduler is usually exclusively intended for access within the Spring context. * Switch this flag to "true" in order to expose the Scheduler globally. * This is not recommended unless you have an existing Spring application that * relies on this behavior. Note that such global exposure was the accidental * default in earlier Spring versions; this has been fixed as of Spring 2.5.6. */ public void setExposeSchedulerInRepository(boolean exposeSchedulerInRepository) { this.exposeSchedulerInRepository = exposeSchedulerInRepository; } /** * Set whether to wait for running jobs to complete on shutdown. * Default is "false". Switch this to "true" if you prefer * fully completed jobs at the expense of a longer shutdown phase. * @see org.quartz.Scheduler#shutdown(boolean) */ public void setWaitForJobsToCompleteOnShutdown(boolean waitForJobsToCompleteOnShutdown) { this.waitForJobsToCompleteOnShutdown = waitForJobsToCompleteOnShutdown; } @Override public void setBeanName(String name) { this.beanName = name; } @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } //--------------------------------------------------------------------- // Implementation of InitializingBean interface //--------------------------------------------------------------------- @Override public void afterPropertiesSet() throws Exception { if (this.dataSource == null && this.nonTransactionalDataSource != null) { this.dataSource = this.nonTransactionalDataSource; } if (this.applicationContext != null && this.resourceLoader == null) { this.resourceLoader = this.applicationContext; } // Initialize the Scheduler instance... this.scheduler = prepareScheduler(prepareSchedulerFactory()); try { registerListeners(); registerJobsAndTriggers(); } catch (Exception ex) { try { this.scheduler.shutdown(true); } catch (Exception ex2) { logger.debug("Scheduler shutdown exception after registration failure", ex2); } throw ex; } } /** * Create a SchedulerFactory if necessary and apply locally defined Quartz properties to it. * @return the initialized SchedulerFactory */ private SchedulerFactory prepareSchedulerFactory() throws SchedulerException, IOException { SchedulerFactory schedulerFactory = this.schedulerFactory; if (schedulerFactory == null) { // Create local SchedulerFactory instance (typically a StdSchedulerFactory) schedulerFactory = BeanUtils.instantiateClass(this.schedulerFactoryClass); if (schedulerFactory instanceof StdSchedulerFactory stdSchedulerFactory) { initSchedulerFactory(stdSchedulerFactory); } else if (this.configLocation != null || this.quartzProperties != null || this.taskExecutor != null || this.dataSource != null) { throw new IllegalArgumentException( "StdSchedulerFactory required for applying Quartz properties: " + schedulerFactory); } // Otherwise, no local settings to be applied via StdSchedulerFactory.initialize(Properties) } // Otherwise, assume that externally provided factory has been initialized with appropriate settings return schedulerFactory; } /** * Initialize the given SchedulerFactory, applying locally defined Quartz properties to it. * @param schedulerFactory the SchedulerFactory to initialize */ private void initSchedulerFactory(StdSchedulerFactory schedulerFactory) throws SchedulerException, IOException { Properties mergedProps = new Properties(); if (this.resourceLoader != null) { mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_CLASS_LOAD_HELPER_CLASS, ResourceLoaderClassLoadHelper.class.getName()); } if (this.taskExecutor != null) { mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, LocalTaskExecutorThreadPool.class.getName()); } else { // Set necessary default properties here, as Quartz will not apply // its default configuration when explicitly given properties. mergedProps.setProperty(StdSchedulerFactory.PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getName()); mergedProps.setProperty(PROP_THREAD_COUNT, Integer.toString(DEFAULT_THREAD_COUNT)); } if (this.configLocation != null) { if (logger.isDebugEnabled()) { logger.debug("Loading Quartz config from [" + this.configLocation + "]"); } PropertiesLoaderUtils.fillProperties(mergedProps, this.configLocation); } CollectionUtils.mergePropertiesIntoMap(this.quartzProperties, mergedProps); if (this.dataSource != null) { mergedProps.putIfAbsent(StdSchedulerFactory.PROP_JOB_STORE_CLASS, LocalDataSourceJobStore.class.getName()); } // Determine scheduler name across local settings and Quartz properties... if (this.schedulerName != null) { mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.schedulerName); } else { String nameProp = mergedProps.getProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME); if (nameProp != null) { this.schedulerName = nameProp; } else if (this.beanName != null) { mergedProps.setProperty(StdSchedulerFactory.PROP_SCHED_INSTANCE_NAME, this.beanName); this.schedulerName = this.beanName; } } schedulerFactory.initialize(mergedProps); } private Scheduler prepareScheduler(SchedulerFactory schedulerFactory) throws SchedulerException { if (this.resourceLoader != null) { // Make given ResourceLoader available for SchedulerFactory configuration. configTimeResourceLoaderHolder.set(this.resourceLoader); } if (this.taskExecutor != null) { // Make given TaskExecutor available for SchedulerFactory configuration. configTimeTaskExecutorHolder.set(this.taskExecutor); } if (this.dataSource != null) { // Make given DataSource available for SchedulerFactory configuration. configTimeDataSourceHolder.set(this.dataSource); } if (this.nonTransactionalDataSource != null) { // Make given non-transactional DataSource available for SchedulerFactory configuration. configTimeNonTransactionalDataSourceHolder.set(this.nonTransactionalDataSource); } // Get Scheduler instance from SchedulerFactory. try { Scheduler scheduler = createScheduler(schedulerFactory, this.schedulerName); populateSchedulerContext(scheduler); if (!this.jobFactorySet && !(scheduler instanceof RemoteScheduler)) { // Use AdaptableJobFactory as default for a local Scheduler, unless when // explicitly given a null value through the "jobFactory" bean property. this.jobFactory = new AdaptableJobFactory(); } if (this.jobFactory != null) { if (this.applicationContext != null && this.jobFactory instanceof ApplicationContextAware applicationContextAware) { applicationContextAware.setApplicationContext(this.applicationContext); } if (this.jobFactory instanceof SchedulerContextAware schedulerContextAware) { schedulerContextAware.setSchedulerContext(scheduler.getContext()); } scheduler.setJobFactory(this.jobFactory); } return scheduler; } finally { if (this.resourceLoader != null) { configTimeResourceLoaderHolder.remove(); } if (this.taskExecutor != null) { configTimeTaskExecutorHolder.remove(); } if (this.dataSource != null) { configTimeDataSourceHolder.remove(); } if (this.nonTransactionalDataSource != null) { configTimeNonTransactionalDataSourceHolder.remove(); } } } /** * Create the Scheduler instance for the given factory and scheduler name. * Called by {@link #afterPropertiesSet}. * The default implementation invokes SchedulerFactory's {@code getScheduler} * method. Can be overridden for custom Scheduler creation. * @param schedulerFactory the factory to create the Scheduler with * @param schedulerName the name of the scheduler to create * @return the Scheduler instance * @throws SchedulerException if thrown by Quartz methods * @see #afterPropertiesSet * @see org.quartz.SchedulerFactory#getScheduler */ @SuppressWarnings("NullAway") protected Scheduler createScheduler(SchedulerFactory schedulerFactory, @Nullable String schedulerName) throws SchedulerException { // Override thread context ClassLoader to work around naive Quartz ClassLoadHelper loading. Thread currentThread = Thread.currentThread(); ClassLoader threadContextClassLoader = currentThread.getContextClassLoader(); boolean overrideClassLoader = (this.resourceLoader != null && this.resourceLoader.getClassLoader() != threadContextClassLoader); if (overrideClassLoader) { currentThread.setContextClassLoader(this.resourceLoader.getClassLoader()); } try { SchedulerRepository repository = SchedulerRepository.getInstance(); synchronized (repository) { Scheduler existingScheduler = (schedulerName != null ? repository.lookup(schedulerName) : null); Scheduler newScheduler = schedulerFactory.getScheduler(); if (newScheduler == existingScheduler) { throw new IllegalStateException("Active Scheduler of name '" + schedulerName + "' already registered " + "in Quartz SchedulerRepository. Cannot create a new Spring-managed Scheduler of the same name!"); } if (!this.exposeSchedulerInRepository) { // Need to remove it in this case, since Quartz shares the Scheduler instance by default! SchedulerRepository.getInstance().remove(newScheduler.getSchedulerName()); } return newScheduler; } } finally { if (overrideClassLoader) { // Reset original thread context ClassLoader. currentThread.setContextClassLoader(threadContextClassLoader); } } } /** * Expose the specified context attributes and/or the current * ApplicationContext in the Quartz SchedulerContext. */ private void populateSchedulerContext(Scheduler scheduler) throws SchedulerException { // Put specified objects into Scheduler context. if (this.schedulerContextMap != null) { scheduler.getContext().putAll(this.schedulerContextMap); } // Register ApplicationContext in Scheduler context. if (this.applicationContextSchedulerContextKey != null) { if (this.applicationContext == null) { throw new IllegalStateException( "SchedulerFactoryBean needs to be set up in an ApplicationContext " + "to be able to handle an 'applicationContextSchedulerContextKey'"); } scheduler.getContext().put(this.applicationContextSchedulerContextKey, this.applicationContext); } } /** * Start the Quartz Scheduler, respecting the "startupDelay" setting. * @param scheduler the Scheduler to start * @param startupDelay the number of seconds to wait before starting * the Scheduler asynchronously */ protected void startScheduler(final Scheduler scheduler, final int startupDelay) throws SchedulerException { if (startupDelay <= 0) { logger.info("Starting Quartz Scheduler now"); scheduler.start(); } else { if (logger.isInfoEnabled()) { logger.info("Will start Quartz Scheduler [" + scheduler.getSchedulerName() + "] in " + startupDelay + " seconds"); } // Not using the Quartz startDelayed method since we explicitly want a daemon // thread here, not keeping the JVM alive in case of all other threads ending. Thread schedulerThread = new Thread(() -> { try { TimeUnit.SECONDS.sleep(startupDelay); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); // simply proceed } if (logger.isInfoEnabled()) { logger.info("Starting Quartz Scheduler now, after delay of " + startupDelay + " seconds"); } try { scheduler.start(); } catch (SchedulerException ex) { throw new SchedulingException("Could not start Quartz Scheduler after delay", ex); } }); schedulerThread.setName("Quartz Scheduler [" + scheduler.getSchedulerName() + "]"); schedulerThread.setDaemon(true); schedulerThread.start(); } } //--------------------------------------------------------------------- // Implementation of FactoryBean interface //--------------------------------------------------------------------- @Override public Scheduler getScheduler() { Assert.state(this.scheduler != null, "No Scheduler set"); return this.scheduler; } @Override @Nullable public Scheduler getObject() { return this.scheduler; } @Override public Class<? extends Scheduler> getObjectType() { return (this.scheduler != null ? this.scheduler.getClass() : Scheduler.class); } @Override public boolean isSingleton() { return true; } //--------------------------------------------------------------------- // Implementation of SmartLifecycle interface //--------------------------------------------------------------------- @Override public void start() throws SchedulingException { if (this.scheduler != null) { try { startScheduler(this.scheduler, this.startupDelay); } catch (SchedulerException ex) { throw new SchedulingException("Could not start Quartz Scheduler", ex); } } } @Override public void stop() throws SchedulingException { if (this.scheduler != null) { try { this.scheduler.standby(); } catch (SchedulerException ex) { throw new SchedulingException("Could not stop Quartz Scheduler", ex); } } } @Override public boolean isRunning() throws SchedulingException { if (this.scheduler != null) { try { return !this.scheduler.isInStandbyMode(); } catch (SchedulerException ex) { return false; } } return false; } //--------------------------------------------------------------------- // Implementation of DisposableBean interface //--------------------------------------------------------------------- /** * Shut down the Quartz scheduler on bean factory shutdown, * stopping all scheduled jobs. */ @Override public void destroy() throws SchedulerException { if (this.scheduler != null) { logger.info("Shutting down Quartz Scheduler"); this.scheduler.shutdown(this.waitForJobsToCompleteOnShutdown); } } } 我看到的start和你的不一样

<?xml version="1.0" encoding="UTF-8"?> <modelVersion>4.0.0</modelVersion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.14.RELEASE</version> <relativePath/> <groupId>com.tplink.nbu.demo</groupId> <artifactId>basic-spring-boot</artifactId> <version>1.0.0</version> <name>basic-spring-boot</name> <description>Demo project for Spring Boot</description> <java.version>1.8</java.version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>RELEASE</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> </dependencies> <build> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </build> 这是我项目的pom,/* * Copyright (c) 2020, TP-Link Co.,Ltd. All rights reserved. */ package com.tplink.nbu.demo.basicspringboot.controller; import javax.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.tplink.nbu.demo.basicspringboot.annotation.UserAuth; import com.tplink.nbu.demo.basicspringboot.bean.UserInfo; import com.tplink.nbu.demo.basicspringboot.component.EmailDuplicateCounter; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginSuccessDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserRegisterDTO; import com.tplink.nbu.demo.basicspringboot.exception.EmailExistsException; import com.tplink.nbu.demo.basicspringboot.exception.MaxRegisterExceededException; import com.tplink.nbu.demo.basicspringboot.exception.UnauthorizedException; import com.tplink.nbu.demo.basicspringboot.service.UserService; /** * @author [email protected] * @version 1.0 * @since 2020/7/13 */ @Slf4j @RestController @RequestMapping("/user") public class UserLogInOutController { @Autowired private UserService userService; // 注入最大注册数配置 @Value("${system.user.max-register}") private int maxRegister; private EmailDuplicateCounter emailDuplicateCounter; // 登录接口 @PostMapping("/login") public UserLoginSuccessDTO login(@Valid @RequestBody UserLoginDTO loginDTO) { boolean auth = userService.auth(loginDTO); if (!auth) { throw new UnauthorizedException(); } log.info("{} login", loginDTO.getUsernameOrEmail()); return UserLoginSuccessDTO.builder() .token(loginDTO.getUsernameOrEmail()) .build(); } // 新增注册接口 @PostMapping("/register") public String register(@Valid @RequestBody UserRegisterDTO registerDTO) { // 邮箱是否重复 if (userService.existsByEmail(registerDTO.getEmail())) { // 触发metrics统计 emailDuplicateCounter.increment(); throw new EmailExistsException(); } // 是否超过最大注册数 if (userService.count() >= maxRegister) { throw new MaxRegisterExceededException(); } // 注册 userService.register(registerDTO); return "REGISTERED SUCCESSFULLY"; } // 登出接口 @UserAuth @PostMapping("/logout") public UserInfo logout(UserInfo userInfo) { log.info("{} logout", userInfo.getUsername()); return userInfo; } } 这是项目的controller,里面有三个http接口,其中server实现类为/* * Copyright (c) 2020, TP-Link Co.,Ltd. All rights reserved. */ package com.tplink.nbu.demo.basicspringboot.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserRegisterDTO; import com.tplink.nbu.demo.basicspringboot.entity.User; import com.tplink.nbu.demo.basicspringboot.repository.UserRepository; /** * @author [email protected] * @version 1.0 * @since 2020/7/13 */ @Service public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Override public boolean auth(UserLoginDTO loginDTO) { // 通过用户名查询用户 User user = userRepository.findByUsername(loginDTO.getUsernameOrEmail()); // 若用户名查不到,再通过邮箱查询 if (user == null) { user = userRepository.findByEmail(loginDTO.getUsernameOrEmail()); } return user != null && user.getPassword().equals(loginDTO.getPassword()); } // 注册逻辑 @Override public void register(UserRegisterDTO registerDTO) { User user = new User(); user.setUsername(registerDTO.getUsername()); user.setPassword(registerDTO.getPassword()); user.setEmail(registerDTO.getEmail()); user.setAddress(registerDTO.getAddress()); userRepository.save(user); } // 统计用户总数 @Override public long count() { return userRepository.count(); } // 校验邮箱是否已存在 @Override public boolean existsByEmail(String email) { return userRepository.existsByEmail(email); } } 现在我想基于 gRPC 实现原有的 HTTP 接口;然后实现一个 gRPC 客户端,用于发送请求和接收响应; 并且服务调用方式为异步非阻塞(基于 Future-Listener)。告诉我具体的步骤和所有需要注意和修改的点

<?xml version="1.0" encoding="UTF-8"?> 4.0.0 org.springframework.boot spring-boot-starter-parent 2.1.14.RELEASE com.tplink.nbu.demo basic-spring-boot 1.0.0 basic-spring-boot Demo project for Spring Boot <java.version>1.8</java.version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>RELEASE</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> </dependencies> <build> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </build> 这是我项目的pom,/* * Copyright (c) 2020, TP-Link Co.,Ltd. All rights reserved. */ package com.tplink.nbu.demo.basicspringboot.controller; import javax.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.tplink.nbu.demo.basicspringboot.annotation.UserAuth; import com.tplink.nbu.demo.basicspringboot.bean.UserInfo; import com.tplink.nbu.demo.basicspringboot.component.EmailDuplicateCounter; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginSuccessDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserRegisterDTO; import com.tplink.nbu.demo.basicspringboot.exception.EmailExistsException; import com.tplink.nbu.demo.basicspringboot.exception.MaxRegisterExceededException; import com.tplink.nbu.demo.basicspringboot.exception.UnauthorizedException; import com.tplink.nbu.demo.basicspringboot.service.UserService; /** @author [email protected] @version 1.0 @since 2020/7/13 */ @Slf4j @RestController @RequestMapping(“/user”) public class UserLogInOutController { @Autowired private UserService userService; // 注入最大注册数配置 @Value(“${system.user.max-register}”) private int maxRegister; private EmailDuplicateCounter emailDuplicateCounter; // 登录接口 @PostMapping(“/login”) public UserLoginSuccessDTO login(@Valid @RequestBody UserLoginDTO loginDTO) { boolean auth = userService.auth(loginDTO); if (!auth) { throw new UnauthorizedException(); } log.info("{} login", loginDTO.getUsernameOrEmail()); return UserLoginSuccessDTO.builder() .token(loginDTO.getUsernameOrEmail()) .build(); } // 新增注册接口 @PostMapping(“/register”) public String register(@Valid @RequestBody UserRegisterDTO registerDTO) { // 邮箱是否重复 if (userService.existsByEmail(registerDTO.getEmail())) { // 触发metrics统计 emailDuplicateCounter.increment(); throw new EmailExistsException(); } // 是否超过最大注册数 if (userService.count() >= maxRegister) { throw new MaxRegisterExceededException(); } // 注册 userService.register(registerDTO); return “REGISTERED SUCCESSFULLY”; } // 登出接口 @UserAuth @PostMapping(“/logout”) public UserInfo logout(UserInfo userInfo) { log.info(“{} logout”, userInfo.getUsername()); return userInfo; } } 这是项目的controller,里面有三个http接口,其中server实现类为/* Copyright © 2020, TP-Link Co.,Ltd. All rights reserved. */ package com.tplink.nbu.demo.basicspringboot.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.tplink.nbu.demo.basicspringboot.dto.UserLoginDTO; import com.tplink.nbu.demo.basicspringboot.dto.UserRegisterDTO; import com.tplink.nbu.demo.basicspringboot.entity.User; import com.tplink.nbu.demo.basicspringboot.repository.UserRepository; /** @author [email protected] @version 1.0 @since 2020/7/13 */ @Service public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Override public boolean auth(UserLoginDTO loginDTO) { // 通过用户名查询用户 User user = userRepository.findByUsername(loginDTO.getUsernameOrEmail()); // 若用户名查不到,再通过邮箱查询 if (user == null) { user = userRepository.findByEmail(loginDTO.getUsernameOrEmail()); } return user != null && user.getPassword().equals(loginDTO.getPassword()); } // 注册逻辑 @Override public void register(UserRegisterDTO registerDTO) { User user = new User(); user.setUsername(registerDTO.getUsername()); user.setPassword(registerDTO.getPassword()); user.setEmail(registerDTO.getEmail()); user.setAddress(registerDTO.getAddress()); userRepository.save(user); } // 统计用户总数 @Override public long count() { return userRepository.count(); } // 校验邮箱是否已存在 @Override public boolean existsByEmail(String email) { return userRepository.existsByEmail(email); } } 现在我想基于 gRPC 实现原有的 HTTP 接口;然后实现一个 gRPC 客户端,用于发送请求和接收响应; 并且服务调用方式为异步非阻塞(基于 Future-Listener)。根据我现有代码,告诉我具体的步骤和所有需要注意和修改的点!注意包的结构,告诉我每一步都需要干什么,我是新手

/* * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web.reactive.function.client.support; import java.net.URI; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.util.Assert; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.service.invoker.AbstractReactorHttpExchangeAdapter; import org.springframework.web.service.invoker.HttpRequestValues; import org.springframework.web.service.invoker.HttpServiceProxyFactory; import org.springframework.web.service.invoker.ReactiveHttpRequestValues; import org.springframework.web.service.invoker.ReactorHttpExchangeAdapter; import org.springframework.web.util.UriBuilderFactory; /** * {@link ReactorHttpExchangeAdapter} that enables an {@link HttpServiceProxyFactory} * to use {@link WebClient} for request execution. * * Use static factory methods in this class to create an * {@code HttpServiceProxyFactory} configured with a given {@code WebClient}. * * @author Rossen Stoyanchev * @since 6.0 */ public final class WebClientAdapter extends AbstractReactorHttpExchangeAdapter { private final WebClient webClient; /** * Package private constructor. See static factory methods. */ private WebClientAdapter(WebClient webClient) { this.webClient = webClient; } @Override public boolean supportsRequestAttributes() { return true; } @Override public Mono<Void> exchangeForMono(HttpRequestValues requestValues) { return newRequest(requestValues).retrieve().toBodilessEntity().then(); } @Override public Mono<HttpHeaders> exchangeForHeadersMono(HttpRequestValues requestValues) { return newRequest(requestValues).retrieve().toBodilessEntity().map(ResponseEntity::getHeaders); } @Override public <T> Mono<T> exchangeForBodyMono(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) { return newRequest(requestValues).retrieve().bodyToMono(bodyType); } @Override public <T> Flux<T> exchangeForBodyFlux(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) { return newRequest(requestValues).retrieve().bodyToFlux(bodyType); } @Override public Mono<ResponseEntity<Void>> exchangeForBodilessEntityMono(HttpRequestValues requestValues) { return newRequest(requestValues).retrieve().toBodilessEntity(); } @Override public <T> Mono<ResponseEntity<T>> exchangeForEntityMono(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) { return newRequest(requestValues).retrieve().toEntity(bodyType); } @Override public <T> Mono<ResponseEntity<Flux<T>>> exchangeForEntityFlux(HttpRequestValues requestValues, ParameterizedTypeReference<T> bodyType) { return newRequest(requestValues).retrieve().toEntityFlux(bodyType); } @SuppressWarnings({"ReactiveStreamsUnusedPublisher", "unchecked"}) private WebClient.RequestBodySpec newRequest(HttpRequestValues values) { HttpMethod httpMethod = values.getHttpMethod(); Assert.notNull(httpMethod, "HttpMethod is required"); WebClient.RequestBodyUriSpec uriSpec = this.webClient.method(httpMethod); WebClient.RequestBodySpec bodySpec; if (values.getUri() != null) { bodySpec = uriSpec.uri(values.getUri()); } else if (values.getUriTemplate() != null) { UriBuilderFactory uriBuilderFactory = values.getUriBuilderFactory(); if(uriBuilderFactory != null){ URI uri = uriBuilderFactory.expand(values.getUriTemplate(), values.getUriVariables()); bodySpec = uriSpec.uri(uri); } else { bodySpec = uriSpec.uri(values.getUriTemplate(), values.getUriVariables()); } } else { throw new IllegalStateException("Neither full URL nor URI template"); } bodySpec.headers(headers -> headers.putAll(values.getHeaders())); bodySpec.cookies(cookies -> cookies.putAll(values.getCookies())); bodySpec.attributes(attributes -> attributes.putAll(values.getAttributes())); if (values.getBodyValue() != null) { if (values.getBodyValueType() != null) { B body = (B) values.getBodyValue(); bodySpec.bodyValue(body, (ParameterizedTypeReference) values.getBodyValueType()); } else { bodySpec.bodyValue(values.getBodyValue()); } } else if (values instanceof ReactiveHttpRequestValues rhrv) { Publisher<?> body = rhrv.getBodyPublisher(); if (body != null) { ParameterizedTypeReference<?> elementType = rhrv.getBodyPublisherElementType(); Assert.notNull(elementType, "Publisher body element type is required"); bodySpec.body(body, elementType); } } return bodySpec; } /** * Create a {@link WebClientAdapter} for the given {@code WebClient} instance. * @param webClient the client to use * @return the created adapter instance * @since 6.1 */ public static WebClientAdapter create(WebClient webClient) { return new WebClientAdapter(webClient); } }

org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘xmcLcdpItemMapper’ defined in file [D:\h0-lcdp\target\classes\com\hand\xmc\infra\mapper\XmcLcdpItemMapper.class]: io.choerodon.mybatis.MapperException: java.lang.IllegalArgumentException: com.hand.xmc.infra.mapper.XmcLcdpItemMapper.insertListAssignKey对应的实体类com.hand.xmc.domain.entity.XmcLcdpItem中包含多个自动增长列,最多只能有一个! at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1808) ~[spring-beans-6.2.1.jar:6.2.1] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:601) ~[spring-beans-6.2.1.jar:6.2.1] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.1.jar:6.2.1] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:336) ~[spring-beans-6.2.1.jar:6.2.1] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:289) ~[spring-beans-6.2.1.jar:6.2.1] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334) ~[spring-beans-6.2.1.jar:6.2.1] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-6.2.1.jar:6.2.1] package com.hand.xmc.domain.entity; import com.fasterxml.jackson.annotation.JsonInclude; import io.choerodon.mybatis.annotation.ModifyAudit; import io.choerodon.mybatis.annotation.VersionAudit; import io.choerodon.mybatis.domain.AuditDomain; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.math.BigDecimal; import java.util.Date; import lombok.Getter; import lombok.Setter; /** LCDP请求明细项表(XmcLcdpItem)实体类 @author maoxiong @since 2025-08-18 13:32:22 */ @Getter @Setter @ApiModel(“LCDP请求明细项表”) @VersionAudit @ModifyAudit @JsonInclude(value = JsonInclude.Include.NON_NULL) @Table(name = “XMC_LCDP_ITEM”) public class XmcLcdpItem extends AuditDomain { private static final long serialVersionUID = -82884834750033649L; public static final String FIELD_ID = "id"; public static final String FIELD_LCDP_REQUEST_ID = "lcdpRequestId"; public static final String FIELD_PO_ITEM_NO = "poItemNo"; public static final String FIELD_ITEM_QTY = "itemQty"; public static final String FIELD_ITEM_AMOUNT = "itemAmount"; public static final String FIELD_SHIPMENT_DATE = "shipmentDate"; public static final String FIELD_MC_DESC = "mcDesc"; public static final String FIELD_UNIT = "unit"; public static final String FIELD_EQ_ID = "eqId"; public static final String FIELD_EQ_STATUS = "eqStatus"; public static final String FIELD_DELIV_DATE = "delivDate"; public static final String FIELD_CREATED_AT = "createdAt"; public static final String FIELD_UPDATED_AT = "updatedAt"; public static final String FIELD_UPDATED_BY = "updatedBy"; public static final String FIELD_VERSION = "version"; public static final String FIELD_IS_DELETED = "isDeleted"; public static final String FIELD_TENANT_ID = "tenantId"; @ApiModelProperty("主键ID") @Id @GeneratedValue private Long id; @ApiModelProperty(value = "关联的LCDP请求ID", required = true) @NotNull private Long lcdpRequestId; @ApiModelProperty(value = "采购订单行号") private String poItemNo; @ApiModelProperty(value = "数量") private Long itemQty; @ApiModelProperty(value = "金额") private Long itemAmount; @ApiModelProperty(value = "发货日期") private Date shipmentDate; @ApiModelProperty(value = "物料描述") private String mcDesc; @ApiModelProperty(value = "单位") private String unit; @ApiModelProperty(value = "设备ID") private String eqId; @ApiModelProperty(value = "设备状态") private String eqStatus; @ApiModelProperty(value = "交货日期") private Date delivDate; @ApiModelProperty(value = "创建时间") private Date createdAt; @ApiModelProperty(value = "更新时间") private Date updatedAt; @ApiModelProperty(value = "更新人") private String updatedBy; @ApiModelProperty(value = "版本号(用于乐观锁)") private Long version; @ApiModelProperty(value = "逻辑删除标识") private String isDeleted; @ApiModelProperty(value = "租户ID", required = true) @NotNull private Long tenantId; } 怎么改

最新推荐

recommend-type

AI 智能算力平台多架构容器镜像管理部署包 - Harbor

主要用于AI 智能算力平台多架构容器镜像管理,部署包信息如下: 版本:v2.14.0 架构:ARM64 部署包类型:离线部署 依赖:Docker 特性: - 支持国产化操作系统部署 - 支持最新版本特性
recommend-type

基于Django框架开发的猫耳影评网是一个集电影信息展示与用户互动交流于一体的综合性影评平台_通过Python多线程爬虫技术采集并清洗网络电影数据_利用MySQL数据库存储结构化信.zip

基于Django框架开发的猫耳影评网是一个集电影信息展示与用户互动交流于一体的综合性影评平台_通过Python多线程爬虫技术采集并清洗网络电影数据_利用MySQL数据库存储结构化信.zip
recommend-type

前端分析-2023071100789s89

前端分析-2023071100789s89
recommend-type

基于深度学习人脸情绪识别.zip

基于深度学习人脸情绪识别.zip
recommend-type

基于Django框架与PyMySQL数据库驱动构建的现代化Web应用开发平台_高效后端服务_数据管理_用户认证_API接口_安全防护_跨平台兼容_企业级部署_自动化测试_性能优化_.zip

基于Django框架与PyMySQL数据库驱动构建的现代化Web应用开发平台_高效后端服务_数据管理_用户认证_API接口_安全防护_跨平台兼容_企业级部署_自动化测试_性能优化_.zip
recommend-type

Moon: 提升团队工作效率的网络界面

从给定的文件信息中,我们可以提取并详细阐释以下知识点: ### 标题知识点 #### Moon 网络界面 1. **定义团队状态**: Moon 应用程序提供了一个界面,用户可以据此定义自己的状态,如在线、忙碌、离开或离线。这一功能有助于团队成员了解彼此的可用性,从而减少不必要的打扰,提高工作效率。 2. **时间可用性管理**: Moon 旨在管理用户的时间可用性。通过提供一个平台来显示团队成员的状态,可以减少对工作流程的干扰,使团队能够更专注于手头的任务。 ### 描述知识点 #### 安装和使用Moon应用程序 1. **安装过程**: Moon应用程序通过使用Docker进行安装和运行,这是一种流行的容器化平台,允许开发者打包应用及其依赖于一个可移植的容器中,简化了部署过程。 - 使用git clone命令从GitHub克隆Moon项目的仓库。 - 进入克隆的项目目录。 - 使用docker build命令构建Moon应用程序的镜像。 - 最后,使用docker run命令运行应用程序。 2. **设置和环境变量**: 在运行Moon应用程序时,需要设置一系列环境变量来指定API的URI、端口和入口点。这些变量帮助应用程序正确地与后端API进行通信。 ### 标签知识点 #### 关键技术栈和应用领域 1. **React**: Moon应用程序很可能使用了React框架来构建其用户界面。React是一个由Facebook开发的前端JavaScript库,用于构建用户界面,尤其是单页应用程序(SPA)。 2. **生产力提升工具**: 从标签“productivity-booster”中我们可以推断,Moon被设计为一种提升个人或团队生产力的工具。它通过减少不必要的通信干扰来帮助用户专注于当前的工作任务。 3. **JavaScript**: 这个标签表明Moon应用程序的前端或后端可能广泛使用了JavaScript编程语言。JavaScript是一种广泛应用于网页开发中的脚本语言,能够实现动态交互效果。 ### 文件名称列表知识点 #### 文件和目录结构 1. **moon-master**: 文件名称“moon-master”暗示了Moon项目的主要目录。通常,“master”表示这是一个主分支或主版本的代码库,它包含了应用程序的核心功能和最新的开发进展。 ### 综合知识点 #### Moon 应用程序的价值和目标 - **提高专注度**: Moon应用程序允许用户设置特定的专注时间,这有助于提高工作效率和质量。通过将注意力集中在特定任务上,可以有效地降低多任务处理时的认知负荷和可能的干扰。 - **优化团队协作**: 明确的团队状态标识有助于减少不必要的沟通,从而使得团队成员能够在各自专注的时间内高效工作。这种管理方式还可以在团队中培养一种专注于当前任务的文化。 - **简洁性和易用性**: Moon的界面设计被描述为“漂亮”,这表明除了功能性外,用户界面的美观和简洁性也是该应用程序的重点,这有助于提高用户体验。 综上所述,Moon应用程序是一个旨在通过网络界面帮助用户管理个人和团队状态的工具,利用Docker进行简洁的部署,强化工作中的专注度,并通过简化团队状态的沟通,提升整体生产力。
recommend-type

远程控制ESP32-CAM机器人汽车及相关库的使用

# 远程控制ESP32 - CAM机器人汽车及相关库的使用 ## 1. 远程控制ESP32 - CAM机器人汽车 ### 1.1 硬件连接 ESP32 - CAM机器人汽车的硬件连接涉及多个组件,具体连接方式如下表所示: | 组件 | 连接到 | 再连接到 | | --- | --- | --- | | TB6612FNG VM | 18650电池正极 | LM2596 IN正极 | | TB6612FNG VCC | ESP32 - CAM VCC (3.3V) | - | | TB6612FNG GND | 18650电池负极 | LM2596 IN负极 | | TB6612FNG A1
recommend-type

CFE层流结构

### CFE层流结构在流量计中的定义和作用 在流量计中,CFE通常指 **Core Flow Executive** 或 **Control Flow Executive**,其“层流结构”(Laminar Flow Structure)是流量计内部用于实现高精度流体测量的核心部件之一。该结构的设计基于流体力学中的层流原理,通过特定几何形状的通道,使流体在通过时形成稳定的层流状态,从而便于测量流体的体积或质量流量。 层流结构通常由多个平行微通道或蜂窝状结构组成,其主要作用是消除流体流动中的湍流效应,确保流体以均匀、稳定的速度分布通过测量区域。这种设计显著提高了流量计的线性度和测量精度,尤
recommend-type

网络货币汇率计算器:实时汇率API应用

货币汇率计算器是一个实用的网络应用程序,它能够帮助用户进行不同货币之间的汇率计算。在这个应用中,用户可以输入一定数量的源货币金额,选择相应的货币对,然后计算出目标货币的等值金额。该应用程序主要涉及到前端技术的实现,包括HTML、CSS和JavaScript,这些技术在网页设计和开发中起着至关重要的作用。下面我们将详细介绍这些技术,以及如何使用这些技术开发货币汇率计算器。 ### HTML (HyperText Markup Language) HTML是构建网页内容的标记语言,是网页的基础。它通过一系列的标签(elements)来定义网页的结构和内容。在货币汇率计算器中,HTML用于创建用户界面,比如输入框、按钮和结果显示区域。HTML标签用于定义各种元素,例如: - `<form>`:用于创建一个表单,用户可以在此输入数据,比如货币金额和货币对。 - `<input>`:用于创建输入字段,用户可以在其中输入要转换的金额。 - `<button>`:用于创建按钮,用户点击按钮后触发汇率计算功能。 - `<span>` 或 `<div>`:用于创建显示计算结果的区域。 ### CSS (Cascading Style Sheets) CSS是一种样式表语言,用于设置网页的视觉格式,如布局、颜色、字体等。在货币汇率计算器中,CSS用来美化界面,提供良好的用户体验。CSS可能被用来: - 设置表单和按钮的样式,比如颜色、字体大小、边距和对齐。 - 定义结果展示区域的背景、文字颜色和字体样式。 - 响应式设计,确保应用在不同大小的屏幕上都可正确显示。 ### JavaScript JavaScript是一种在浏览器中运行的编程语言,它使网页可以交互,执行各种操作。在货币汇率计算器中,JavaScript负责处理用户输入、调用汇率API以及展示计算结果。JavaScript可能需要完成以下功能: - 获取用户输入的金额和选择的货币对。 - 调用一个汇率API来获取实时的货币汇率数据。 - 将获取到的汇率数据进行处理,并计算出目标货币的金额。 - 更新网页上的结果显示区域,展示最终的计算结果。 ### 使用汇率API 应用程序使用汇率API来显示数据,API(Application Programming Interface,应用程序编程接口)是一个使软件应用之间能够进行交互的接口。在货币汇率计算器中,需要注册并使用某个提供实时汇率信息的API服务。通过发送请求到API,并接收API返回的JSON或XML格式数据,应用程序可以获取到当前的汇率信息,并进行计算。 ### 开发货币汇率计算器的步骤 1. **项目准备**:创建项目文件夹,设置基础的HTML结构。 2. **界面设计**:使用HTML构建用户界面,用CSS进行样式设计。 3. **功能实现**:编写JavaScript代码,处理用户输入和调用汇率API。 4. **测试与调试**:确保应用在不同的浏览器和设备上运行无误。 5. **部署上线**:将应用程序部署到服务器上,供用户访问。 6. **维护更新**:根据用户反馈和市场汇率波动,定期更新应用。 ### 贡献与许可 该文档还提到了如何为该项目贡献代码。首先需要将项目克隆到本地计算机,然后创建一个新的分支进行修改或增加功能,之后将分支推送到自己的GitHub仓库,并向原项目提交一个拉取请求(Pull Request)。此外,文档提到了项目的许可信息,但具体的内容未在摘要中给出。 总结以上内容,货币汇率计算器是基于前端技术实现的一个应用程序,通过HTML、CSS和JavaScript技术构建用户界面并实现功能,它依赖于外部的汇率API来获取实时数据。开发者可以遵循文档中给出的步骤对项目进行贡献,并遵守项目的许可协议。
recommend-type

蓝牙低功耗(BLE)信标与通信技术详解

### 蓝牙低功耗(BLE)信标与通信技术详解 #### 1. BLE信标数据设置 在BLE应用中,信标数据的设置是关键步骤。以下是一段设置信标数据的代码示例: ```cpp beaconData[11] = 0xAD; beaconData[12] = 0x0C; // UUID Instance BID[0 to 5] beaconData[13] = 0xFA; // 0cfa43d07079 beaconData[14] = 0x43; beaconData[15] = 0xD0; beaconData[16] = 0x70; beaconData[17] = 0x79;