[Spring Boot] Expense API 实现

news2025/3/12 19:24:34

[Spring Boot] Expense API 实现

项目地址:expense-api

项目简介

最近跟着视频做的一个 spring boot 的项目,包含了比较简单的记账功能的实现(只限 API 部分),具体实现的功能有:

  • 记账(expenses API)
  • 类型(categories API)
  • 用户(登录/注册/更新/删除 API)

整体的验证是通过 JWT Token 去实现的,没有实现管理员权限。

使用了:

  • java17
  • spring boot 3.4.1
  • json web token 0.12.6
  • map struct 1.6.3
  • lombok 1.18.36
  • lombok map struct binding 0.2.0
  • docker

运行方式可以直接跑根目录下的 start.sh,脚本会运行 mvnw clean package 指令打包 spring boot jar 文件。然后运行对应的 docker compose 文件生成对应的容器:

docker ps
CONTAINER ID   IMAGE                                            COMMAND                  CREATED        STATUS                  PORTS                               NAMES
a9fd6ebf70ec   expense-tracker-springboot-expense-tracker-api   "java -jar /expense_…"   14 hours ago   Up 14 hours             0.0.0.0:8080->8080/tcp              springboot-expense-tracker-api
fc2f4f82f581   mysql:8.3.0                                      "docker-entrypoint.s…"   14 hours ago   Up 14 hours (healthy)   0.0.0.0:3306->3306/tcp, 33060/tcp   mysql-expense-tracker

然后可以通过 postman 导入存在根目录下的 Expense Manager API.postman_collection.json 进行 API 的测试:

在这里插入图片描述

总体来说这次是把之前断断续续学的 spring boot 3 通过做项目的方式进行了一个整合,并且简单的学习了一下 DTO 模式和基于 JWT 实现的用户验证。之前在 [spring] rest api security 中学习的是使用默认的 Spring Security 进行用户验证,这次使用了 CustomUserDetailsService,可以使用更加灵活的数据库结构。

项目的数据关系如下:

在这里插入图片描述

⚠️:task 是做的 demo 对象,和实际的项目没什么关系

基础结构

简单的梳理一下项目中用的各种项目结构和模式

MVC 结构

MVC 是一个非常传统的数据结构了,具体作用如下:

  • Model,数据和业务逻辑层

    model 层负责和数据库的交互,对数据的业务处理、数据的验证、数据的操作之类数据相关的部分

  • View,即 UI 部分

    前后端分离的话这部分选择很多,不仅仅单指网页端

    比如说手机 app、电脑 app,需要联网操作和 API 进行数据交互的,都是 view 层

    以网页来说,现在最流行的就是 React/View/Angular,如果前后端不分离的话,目前最流行的应该是 thymeleaf

  • Controller,负责 Model 和 View 层的交互

    controller 主要会接受请求,并且返回 response

这个项目里实现的是 Model 和 Controller 部分的内容。controller 部分的代码比较直接简单,以 category 为例:

@RestController
@RequestMapping("/categories")
@RequiredArgsConstructor
public class CategoryController {
    private final CategoryService categoryService;
    private final CategoryMapper categoryMapper;

    @ResponseStatus(HttpStatus.CREATED)
    @PostMapping
    public CategoryResponse createCategory(@RequestBody CategoryRequest categoryRequest) {
        CategoryDTO categoryDTO = categoryMapper.mapToCategoryDTO(categoryRequest);
        categoryDTO = categoryService.saveCategory(categoryDTO);
        return categoryMapper.mapToCategoryResponse(categoryDTO);
    }

    @GetMapping
    public List<CategoryResponse> readCategories() {
        List<CategoryDTO> list = categoryService.getAllCategories();
        return list.stream().map(categoryMapper::mapToCategoryResponse).collect(Collectors.toList());
    }

    @ResponseStatus(HttpStatus.NO_CONTENT)
    @DeleteMapping("/{categoryId}")
    public void deleteCategory(@PathVariable String categoryId) {
        categoryService.deleteCategory(categoryId);
    }
}

其中 @PostMapping, @DeleteMapping 分别对应的是 HTTP 请求中的 method,即 CRUD 的操作。具体的操作则是通过调用 service 层中对应的方法去实现,controller 并不在乎。最终将 service 层中返回的数据,并通过 DTO 进行 mapping,作为 response 返回给用户。

而在比较新的 spring boot 项目中,Model 层的耦合度较高,因此也会使用其他的不同模式进行实现,这个项目中使用的就是 entity+service+repository 的实现去解决这个问题

entity

entity 表现了数据结构

这个数据结构即使 Java 数据的结构,也是数据库中的对应结构,如:

@Entity
@Table(name = "tbl_categories")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class CategoryEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "category_id", unique = true)
    private String categoryId;

    @Column(unique = true)
    @NotBlank(message = "Category name must not be empty.")
    @Size(min = 3, message = "Category name must be at least 3 characters.")
    private String name;
    private String description;

    @Column(name = "category_icon")
    private String categoryIcon;

    @Column(name = "created_at", nullable = false, updatable = false)
    @CreationTimestamp
    private Timestamp createdAt;

    @Column(name = "updated_at")
    @UpdateTimestamp
    private Timestamp updatedAt;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "user_id", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private User user;
}

entity 完成了 POJO 与数据库的 table 数据的映射

除此之外,需要注意的几个点有:

  • entity 本身不应该包含任何的业务逻辑

  • entity 最好不要直接暴露给 API

    考虑到 user 作为使用情况,用户在登录/注册的情况下可以选择使用用户名+密码的搭配,但是在获取用户信息的时候显然是不需要获得这个信息的,因此密码这个信息就不应该包含在在 json 中传给调用 API 的用户

    的确可以使用 @JsonIgnore 将密码从 parse json 中这个过程中去除掉,这个的问题是,在使用 registerlogin 这样的 endpoint 也会把密码去掉,那么登陆的功能也就无法实现

    现在一个比较流行的模式是使用 DTO 去实现

Service 层模式

service 层主要负责负责业务逻辑的处理,包括但不限于:

  • 将获取的 DTO 转换成对应的 entity

    这也包含一些关联数据的映射,以 expense 为例,它是有一个对 user 的关联的。因此在 service 层时,就可以讲关联数据进行映射

  • 数据验证及转换

    数据验证简单的可以通过 @Entity 去实现,稍微复杂的还是需要手动验证,比如根据不同的市场判断最大最小值、时区的转换,将数据转成 big decimal 进行存储——我们项目里也有一个类似的逻辑,目前对于数据的精度和范围要求比较高,直接使用 JavaScript 的整数类型会造成 overflow,所以最后采取了 string 转换+使用 decimal.js 进行计算的方法去解决。这时候后端就需要将前端传来的字符串转化成 big decimal 进行存储

  • 处理异常

    这个就不多赘述了

service 的实现大体如下:

@Service
@RequiredArgsConstructor
public class ExpenseServiceImpl implements ExpenseService {
    private final ExpenseRepository expenseRepo;
    private final UserService userService;
    private final CategoryRepository categoryRepository;
    private final ExpenseMapper expenseMapper;

    @Override
    public List<ExpenseDTO> getAllExpenses(Pageable page) {
        List<ExpenseEntity> expenseList = expenseRepo.findByUserId(userService.getLoggedInUser().getId(), page).toList();
        return expenseList.stream().map(expenseMapper::mapToExpenseDTO).collect(Collectors.toList());
    }

    @Override
    public ExpenseDTO getExpenseById(String expenseId) {
        ExpenseEntity existingExpense = getExpenseEntity(expenseId);
        return expenseMapper.mapToExpenseDTO(existingExpense);
    }

    private ExpenseEntity getExpenseEntity(String expenseId) {
        Optional<ExpenseEntity> expense = expenseRepo.findByUserIdAndExpenseId(userService.getLoggedInUser().getId(), expenseId);

        if (expense.isEmpty()) {
            throw new ResourceNotFoundException("Expense is not found for the id " + expenseId);
        }
        return expense.get();
    }

    @Override
    public void deleteExpenseById(String expenseId) {
        ExpenseEntity expense = getExpenseEntity(expenseId);
        expenseRepo.delete(expense);
    }

    @Override
    public ExpenseDTO saveExpenseDetails(ExpenseDTO expenseDTO) {
        // check the existence of category
        Optional<CategoryEntity> optionalCategory = categoryRepository.findByUserIdAndCategoryId(userService.getLoggedInUser()
                .getId(), expenseDTO.getCategoryId());
        if (optionalCategory.isEmpty()) {
            throw new ResourceNotFoundException("Category not found for the id " + expenseDTO.getCategoryId());
        }

        expenseDTO.setExpenseId(UUID.randomUUID().toString());

        // map to entity object
        ExpenseEntity newExpense = expenseMapper.mapToExpenseEntity(expenseDTO);
        newExpense.setCategory(optionalCategory.get());
        newExpense.setUser(userService.getLoggedInUser());
        newExpense = expenseRepo.save(newExpense);

        return expenseMapper.mapToExpenseDTO(newExpense);
    }

    @Override
    public ExpenseDTO updateExpenseDetails(String expenseId, ExpenseDTO expenseDTO) {
        ExpenseEntity existingExpense = getExpenseEntity(expenseId);

        if (expenseDTO.getCategoryId() != null) {
            String categoryId = expenseDTO.getCategoryId();
            Optional<CategoryEntity> optionalCategory = categoryRepository.findByUserIdAndCategoryId(userService.getLoggedInUser()
                    .getId(), categoryId);
            if (optionalCategory.isEmpty()) {
                throw new ResourceNotFoundException("Category not found for the id" + categoryId);
            }
            existingExpense.setCategory(optionalCategory.get());
        }

        Optional.ofNullable(expenseDTO.getName()).ifPresent(existingExpense::setName);
        Optional.ofNullable(expenseDTO.getDescription()).ifPresent(existingExpense::setDescription);
        Optional.ofNullable(expenseDTO.getAmount()).ifPresent(existingExpense::setAmount);
        Optional.ofNullable(expenseDTO.getDate()).ifPresent(existingExpense::setDate);
        existingExpense = expenseRepo.save(existingExpense);

        return expenseMapper.mapToExpenseDTO(existingExpense);
    }

    @Override
    public List<ExpenseDTO> readByCategory(String category, Pageable page) {
        Optional<CategoryEntity> optionalCategory = categoryRepository.findByNameAndUserId(category, userService.getLoggedInUser()
                .getId());
        if (optionalCategory.isEmpty()) {
            throw new ResourceNotFoundException("Category not found for the name " + category);
        }

        return expenseRepo.findByUserIdAndCategoryId(userService.getLoggedInUser().getId(), optionalCategory.get()
                .getId(), page).toList().stream().map(expenseMapper::mapToExpenseDTO).collect(Collectors.toList());
    }

    @Override
    public List<ExpenseDTO> readByName(String name, Pageable page) {
        List <ExpenseEntity> list = expenseRepo.findByUserIdAndNameContaining(userService.getLoggedInUser().getId(), name, page).toList();
        return list.stream().map(expenseMapper::mapToExpenseDTO).collect(Collectors.toList());
    }

    @Override
    public List<ExpenseDTO> readByDate(Date startDate, Date endDate, Pageable page) {
        if (startDate == null) {
            startDate = new Date(0);
        }

        if (endDate == null) {
            endDate = new Date(System.currentTimeMillis());
        }

        return expenseRepo.findByUserIdAndDateBetween(userService.getLoggedInUser().getId(), startDate, endDate, page)
                .toList().stream().map(expenseMapper::mapToExpenseDTO).collect(Collectors.toList());
    }
}

大多数情况下 service 在实现的时候会采取新建一个 interface 定义大多数需要的方法,然后再实现 impl,主要的原因也是因为一个 service 可以有不同的实现,可以根据具体的业务调用对应的实现——让 spring 自己去判断调用合适的实现

Repository 模式

repository 则主要负责对数据库进行管理、交流

大多数情况下使用默认的方法就够了,偶尔也会需要重写一下 query

实现大体如下:

// for more details: https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html
@Repository
public interface ExpenseRepository extends JpaRepository<ExpenseEntity, Long> {
    // // SELECT * FROM tbl_expenses WHERE category=?
    // Page<Expense> findByCategory(String category, Pageable page);

    // // SELECT * FROM tbl_expenses WHERE name LIKE '%keyword%'
    // Page<Expense> findByNameContaining(String keyword, Pageable page);

    // // SELECT * FROM tbl_expenses WHERE date BETWEEN 'startDate AND 'endDate'
    // Page<Expense> findByDateBetween(Date startDate, Date endDate, Pageable page);

    // SELECT * FROM tbl_expenses WHERE user_id=? AND category=?
    Page<ExpenseEntity> findByUserIdAndCategory(Long userId, String category, Pageable page);

    Page<ExpenseEntity> findByUserIdAndCategoryId(Long userId, Long categoryId, Pageable page);

    // SELECT * FROM tbl_expenses WHERE user_id=? AND name LIKE '%keyword%'
    Page<ExpenseEntity> findByUserIdAndNameContaining(Long userId, String keyword, Pageable page);

    // SELECT * FROM tbl_expenses WHERE user_id=? AND date BETWEEN 'startDate AND 'endDate'
    Page<ExpenseEntity> findByUserIdAndDateBetween(Long userId, Date startDate, Date endDate, Pageable page);

    // SELECT * FROM tbl_expenses WHERE user_id=?
    Page<ExpenseEntity> findByUserId(Long userId, Pageable page);

    // SELECT * FROM tbl_expenses WHERE user_id=? AND id=?
    Optional<ExpenseEntity> findByUserIdAndExpenseId(Long userId, String expenseId);
}

PS:需要使用 interface 去 extend 其他的 repository,这里是 JpaRepository,不同的数据库 extend 不同的 repository,mongo 的则是 extend MongoRepository

DTO 模式

DTO 全称 Data Transfer Object,顾名思义是在不同层级中对数据进行转换,因此它在 Model 和 Controller 中用的都比较频繁

它主要的作用在 Entity 中提到了,就是为了将数据处理/转换成合适的格式

之前 DTO 用的是 Lombok 提供的 @Builder 的工厂模式实现的,官方文档下的使用方式为:

Person.builder()
.name("Adam Savage")
.city("San Francisco")
.job("Mythbusters")
.job("Unchained Reaction")
.build();

不过相对而言这种转换方式还是比较麻烦的,最终跟着教程熟悉了一下 mapstruct 的使用方式,具体的 mapper 实现为:

@Mapper(componentModel = "spring")
public interface CategoryMapper {
    CategoryMapper INSTANCE = Mappers.getMapper(CategoryMapper.class);

    CategoryEntity mapToCategoryEntity(CategoryDTO categoryDTO);

    CategoryDTO mapToCategoryDTO(CategoryEntity categoryEntity);

    @Mapping(target = "categoryIcon", source = "categoryRequest.icon")
    CategoryDTO mapToCategoryDTO(CategoryRequest categoryRequest);

    CategoryResponse mapToCategoryResponse(CategoryDTO categoryDTO);
}

需要注意的是,每次修改完代码需要重新 build 一下 maven 项目,这样才能够重新生成对应的 CategoryMapper

需要注意的是,这里的 targetCategoryDTO,这也是转换结果中的属性。与之相对的 source 就是 CategoryRequest

用户验证

使用的是 jwt 验证方式,基于 jwt 的验证因为是无状态的,所以从 token 中获取用户名后就会添加到 userDetails 中,具体的认证是通过 signature 实现的,如下:

在这里插入图片描述

用户名之类的信息其实是公开的,具体验证 signature 的方法,则是需要获取 secret,通过加密/解密进行对比验证。因此对于使用 jwt token 进行验证的 app 来说,这个 secret/private key 是非常重要的

一旦 secret/private key 泄露,那么就可以通过它去获取对应的 signature,从而绕过验证

util

util 主要实现的就是 jwt 相关的部分,主要包活生成 jwt token 和验证 jwt token

@Component
public class JwtTokenUtil {

    private static final int JWT_TOKEN_VALIDITY = 5 * 60 * 60;

    @Value("${jwt.secret}")
    private String secret;

    private SecretKey getSignInKey() {
        byte[] keyBytes = Decoders.BASE64.decode(secret);
        return Keys.hmacShaKeyFor(keyBytes);
    }

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();

        return Jwts.builder()
                .claims(claims)
                .subject(userDetails.getUsername())
                .issuedAt(new Date(System.currentTimeMillis()))
                .expiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
                .signWith(getSignInKey(), Jwts.SIG.HS256)
                .compact();
    }

    public String getUsernameFromToken(String jwtToken) {
        return getClaimFromToken(jwtToken, Claims::getSubject);
    }

    private <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
        if (token.startsWith("Bearer ")) {
            token = token.substring(7).trim(); // Remove 'Bearer ' and trim any extra spaces
        }

        final Claims claims = Jwts.parser()
                .verifyWith(getSignInKey())
                .build()
                .parseSignedClaims(token)
                .getPayload();

        return claimsResolver.apply(claims);
    }

    public boolean validateToken(String jwtToken, UserDetails userDetails) {
        final String username = getUsernameFromToken(jwtToken);

        return username.equals(userDetails.getUsername()) && !isTokenExpired(jwtToken);
    }

    private boolean isTokenExpired(String jwtToken) {
        final Date expiration = getExpirationDateFromToken(jwtToken);

        return expiration.before(new Date());
    }

    private Date getExpirationDateFromToken(String jwtToken) {
        return getClaimFromToken(jwtToken, Claims::getExpiration);
    }
}

授权

本项目里没有使用 spring boot 自带的 userDetails,而是使用自定义的实现,需要注意的是,自定义的类也需要实现 UserDetailsService,具体地说是 loadUserByUsername 这个方法:

@Service
public class CustomUserDetailsService implements UserDetailsService {
    private final UserRepository userRepository;

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        System.out.println("email: " + email);
        User existingUser = userRepository.findByEmail(email)
                .orElseThrow(() -> new UsernameNotFoundException("User not found for the email: " + email));
        return new org.springframework.security.core.userdetails.User(existingUser.getEmail(), existingUser.getPassword(), new ArrayList<>());
    }
}

之后在使用原本的 userDetails 的地方用 CustomUserDetailsService 即可

需要注意的点是:

  • loadUserByUsername 方法实际上是通过用户名获取数据库中的用户,包括用户密码
  • 这个实现是基于 jwt 验证实现,用的是 stateful/session-based 验证,则会导致别人只需要知道用户名,就能够顺利通过验证

完成用户登录验证后,则需要返回一个 jwt token,具体实现在 controller 中:

    @PostMapping("/login")
    public ResponseEntity<JwtResponse> login(@RequestBody AuthModel authModel) throws Exception {
        authenticate(authModel.getEmail(), authModel.getPassword());

        // used in stateful authentication
        // SecurityContextHolder.getContext().setAuthentication(authentication);

        // generate the jwt token
        final UserDetails userDetails = userDetailsService.loadUserByUsername(authModel.getEmail());
        final String token = jwtTokenUtil.generateToken(userDetails);

        return new ResponseEntity<>(new JwtResponse(token), HttpStatus.OK);
    }

验证

验证部分使用的是 jwt,具体的实现通过以下两个部分:

  1. 实现 jwt token 的 filter

    这一步负责:

    • 从 header 中获取 jwt token,并且对其进行数据处理——移除 Bearer 这个前缀

    • 从获取的 jwt token 中获取用户名和过期时间(expiration date),如果用户名与当前 userDetails 中获取的用户名不符,那么用户便授权失败;如果用户当前登陆时间已过期,那么用户验证失败

    代码实现如下:

    public class JwtRequestFilter extends OncePerRequestFilter {
        @Autowired
        private JwtTokenUtil jwtTokenUtil;
    
        @Autowired
        private CustomUserDetailsService userDetailsService;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                throws ServletException, IOException {
            final String requestTokenHeader = request.getHeader("Authorization");
            String jwtToken = null;
            String username = null;
    
            if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
                jwtToken = requestTokenHeader.substring(7);
    
                try {
                    username = jwtTokenUtil.getUsernameFromToken(jwtToken);
                } catch (IllegalArgumentException e) {
                    throw new RuntimeException("Unable to get JWT Token.");
                } catch (ExpiredJwtException e) {
                    throw new RuntimeException("Jwt token has expired.");
                }
            }
    
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
    
                if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
                    UsernamePasswordAuthenticationToken authToken =
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    
                    authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
    
                    SecurityContextHolder.getContext().setAuthentication(authToken);
                }
            }
    
            filterChain.doFilter(request, response);
        }
    }
    
    
  2. 添加 jwt filter

    这一步是在 Security Config 中添加,主要添加在 SecurityFilterChain 中,具体代码如下:

        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            http.csrf(AbstractHttpConfigurer::disable)
                    .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry ->
                            authorizationManagerRequestMatcherRegistry
                                    .requestMatchers("/login/**", "/register/**").permitAll()
                                    .anyRequest().authenticated())
                    // ---- 注意这里 ----
                    .sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                    // ---- 注意这里 ----
                    .addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class)
                    .httpBasic(Customizer.withDefaults());
    
            return http.build();
        }
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2299676.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设置默认构建变体 Build Variant

Android Studio在打开项目时有时会把我设置好的build Variant改为默认的变体&#xff0c;没注意的话可能打完包才发现打错了&#xff0c;浪费时间。因此&#xff0c;有必要通过代码设置一个我想要的默认变体。 代码其实很简单&#xff0c;只要在变体下面加上isDefault true即可…

【大模型】DeepSeek使用与原理解析:从V3到R1

文章目录 一、引言二、使用与测评1.7大R1使用技巧2.官网实测 发展历程三、Deepseek MoE&#xff1a;专家负载均衡 &#xff08;2024年1月&#xff09;四、GRPO&#xff1a;群体相对策略优化&#xff08;DeepSeek-Math&#xff0c;2024年4月&#xff09;五、三代注意力&#xff…

DAY04 Object、Date类、DateFormat类、Calendar类、Math类、System类

学习目标 能够说出Object类的特点是所有类的祖宗类,任意的一个类都直接或者间接的继承了Object类,都可以使用Object类中的方法Animal extends Object:直接继承Cat extends Animal:间接继承 能够重写Object类的toString方法altinsert,选择toString 能够重写Object类的equals方法…

图像生成GAN和风格迁移

文章目录 摘要abstract1.生成对抗网络 GAN1.1 算法步骤 2.风格迁移2.1 损失函数2.2 论文阅读2.2.1 简介2.2.2 方法2.2.3 实验2.2.4 结论 3.总结 摘要 本周学习了生成对抗网络&#xff08;GAN&#xff09;与风格迁移技术在图像生成中的应用。首先介绍了GAN模型中生成器与判别器…

golangAPI调用deepseek

目录 1.deepseek官方API调用文档1.访问格式2.curl组装 2.go代码1. config 配置2.模型相关3.错误处理4.deepseekAPI接口实现5. 调用使用 3.响应实例 1.deepseek官方API调用文档 1.访问格式 现在我们来解析这个curl 2.curl组装 // 这是请求头要加的参数-H "Content-Type:…

【第15章:量子深度学习与未来趋势—15.3 量子深度学习在图像处理、自然语言处理等领域的应用潜力分析】

一、开篇:为什么我们需要关注这场"量子+AI"的世纪联姻? 各位技术爱好者们,今天我们要聊的这个话题,可能是未来十年最值得押注的技术革命——量子深度学习。这不是简单的"1+1=2"的物理叠加,而是一场可能彻底改写AI发展轨迹的范式转移。 想象这样一个…

JAVA安全—Shiro反序列化DNS利用链CC利用链AES动态调试

前言 讲了FastJson反序列化的原理和利用链&#xff0c;今天讲一下Shiro的反序列化利用&#xff0c;这个也是目前比较热门的。 原生态反序列化 我们先来复习一下原生态的反序列化&#xff0c;之前也是讲过的&#xff0c;打开我们写过的serialization_demo。代码也很简单&…

LangChain大模型应用开发:提示词工程应用与实践

介绍 大家好&#xff0c;博主又来给大家分享知识了。今天给大家分享的内容是LangChain提示词工程应用与实践。 在如今火热的大语言模型应用领域里&#xff0c;LangChain可是一个相当强大且实用的工具。而其中的提示词(Prompt)&#xff0c;更是我们与语言模型进行有效沟通的关…

2025 N1CTF crypto 复现

近一个月都没有学习了&#xff0c;一些比赛也没有打&#xff0c;很惭愧自己还是处在刚放假时的水平啊&#xff0c;马上开学了&#xff0c;抓紧做一些训练来康复。 CheckIn import os from Crypto.Util.number import * from secret import FLAGp, q getPrime(512), getPrime…

Windows Defender Control--禁用Windows安全中心

Windows Defender Control--禁用Windows安全中心 链接&#xff1a;https://pan.xunlei.com/s/VOJDuy2ZEqswU4sEgf12JthZA1?pwdtre6#

mount 出现 2038 问题

在 linux 上挂载 ext4 文件系统时出现了 2038 年问题&#xff0c;如下&#xff1a; [ 236.388500] EXT4-fs (mmcblk0p2): mounted filesystem with ordered data mode. Opts: (null) [ 236.388560] ext4 filesystem being mounted at /root/tmp supports timestamps until 2…

【第12章:深度学习与伦理、隐私—12.4 深度学习与伦理、隐私领域的未来挑战与应对策略】

凌晨三点的自动驾驶测试场,AI系统突然在暴雨中做出惊人决策——它选择撞向隔离带而不是紧急变道,因为算法推演发现隔离带后的应急车道站着五个工程师。这个惊悚的伦理困境,揭开了深度学习伦理危机最尖锐的冰山一角。 一、潘多拉魔盒已开:深度学习伦理的四大原罪 1.1 数据原…

RL--2

强化学习当中最难的两个点是&#xff1a; 1.reward delay&#xff1b; 2.agent的行为会影响到之后看到的东西&#xff0c;所以agent要学会探索世界&#xff1b; 关于强化学习的不同类型&#xff0c;可以分为以下三种&#xff1a; 一种是policy based&#xff1a;可以理解为它是…

SpringMVC新版本踩坑[已解决]

问题&#xff1a; 在使用最新版本springMVC做项目部署时&#xff0c;浏览器反复500&#xff0c;如下图&#xff1a; 异常描述&#xff1a; 类型异常报告 消息Request processing failed: java.lang.IllegalArgumentException: Name for argument of type [int] not specifie…

2025 pwn_A_childs_dream

文章目录 fc/sfc mesen下载和使用推荐 fc/sfc https://www.mesen.ca/docs/ mesen2安装&#xff0c;vscode安装zg 任天堂yyds w d 左右移动 u结束游戏 i崩溃或者卡死了 L暂停 D658地方有个flag 发现DEEE会使用他。且只有这个地方&#xff0c;maybe会输出flag&#xff0c;应…

pandas(11 分类数据和数据可视化)

前面内容&#xff1a;pandas(10 日期和Timedelta) 目录 一、Python Pandas 分类数据 1.1 pd.Categorical() 1.2 describe() 1.3 获取类别的属性 1.4 分类操作 1.5 分类数据的比较 二、Python Pandas 数据可视化 2.1 基础绘图&#xff1a;plot 2.2 条形图 2.3 直方…

Redis 03章——10大数据类型概述

一、which10 &#xff08;1&#xff09;一图 &#xff08;2&#xff09;提前声明 这里说的数据类型是value的数据类型&#xff0c;key的类型都是字符串 官网&#xff1a;Understand Redis data types | Docs &#xff08;3&#xff09;分别是 1.3.1redis字符串&#xff0…

bps是什么意思

本文来自DeepSeek "bps" 是 "bits per second" 的缩写&#xff0c;表示每秒传输的比特数&#xff0c;用于衡量数据传输速率。1 bps 即每秒传输 1 比特。 常见单位 bps&#xff1a;比特每秒 Kbps&#xff1a;千比特每秒&#xff08;1 Kbps 1,000 bps&am…

撕碎QT面具(1):Tab Widget转到某个Tab页

笔者未系统学过C语法&#xff0c;仅有Java基础&#xff0c;具体写法仿照于大模型以及其它博客。自我感觉&#xff0c;如果会一门对象语言&#xff0c;没必要先刻意学C&#xff0c;因为自己具有对象语言的基础&#xff0c;等需要用什么再学也不迟。毕竟不是专门学C去搞算法。 1…

项目版本号生成

需求 项目想要生成一个更新版本号&#xff0c;格式为v2.0.20250101。 其中v2.0为版本号&#xff0c;更新时进行配置&#xff1b;20250101为更新日期&#xff0c;版本更新时自动生成。 实现思路 创建一个配置文件version.properties&#xff0c;在其中配置版本号&#xff1b…