Springboot后端简易方式快速搭建

前言

快速学了下。和传统的springboot项目相比,没有用service和serviceImpl。比较不合规,但够简单。可以用于快速开发。

前后端分离。前端请另寻。

特别感谢:程序员青戈

程序员青戈的个人空间_哔哩哔哩_bilibili

1 开始

2 手动导入一些依赖

<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombook 哦对 在项目创建就可以导入了-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--mybatis-plus 解放增删改查-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.1</version>
</dependency>
<!-- JWT Token相关 -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
<!-- 超多好用的工具 强烈推荐 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.3</version>
</dependency>
<!-- 加密工具 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

3 建个数据库

4 application.properties

# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/springboot-vue?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=123456
# 应用服务 WEB 访问端口
server.port=9090

5 返回Result

public class Result<T> {
private String code;
private String msg;
private T data; public String getCode() {
return code;
} public void setCode(String code) {
this.code = code;
} public String getMsg() {
return msg;
} public void setMsg(String msg) {
this.msg = msg;
} public T getData() {
return data;
} public void setData(T data) {
this.data = data;
} public Result() {
} public Result(T data) {
this.data = data;
} public static Result success() {
Result result = new Result<>();
result.setCode("0");
result.setMsg("成功");
return result;
} public static <T> Result<T> success(T data) {
Result<T> result = new Result<>(data);
result.setCode("0");
result.setMsg("成功");
return result;
} public static Result error(String code, String msg) {
Result result = new Result();
result.setCode(code);
result.setMsg(msg);
return result;
}
}

6 文件结构预览

7 entity.User

这里写实体。属性和表一一对应

注意看注释!!

@TableName("user")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
private String username;
private String password;
private String nickName;
private Integer age;
private String sex;
private String address;
// private String avatar;
// @TableField(exist = false)
// private List<Integer> roles;
//
// @TableField(exist = false)
// private List<Book> bookList;
//
// @TableField(exist = false)
// private String token;
//
// private BigDecimal account;
//
// @TableField(exist = false)
// private Set<Permission> permissions;
}

8 mapper.UserMapper

这个BaseMapper自带增删改查。如果有其他需求可以自己写。

public interface UserMapper  extends BaseMapper<User> {
}

9 controller.UserController

可以了解一下restful风格接口。

@RestController
@RequestMapping("/user")
public class UserController {
@Resource
UserMapper userMapper; @PostMapping
public Result<?> save(@RequestBody User user){
userMapper.insert(user);
return Result.success(); }
}

10 体验一下

在上面的controller输入:

@GetMapping("/all")
public Result<?> findAll() {
return Result.success(userMapper.selectList(null));
}

然后访问127.0.0.1:9090/user/all

就能看到数据库的json啦 是不是很简单呢

返回的json交给前端耍了。

(一定要找一个好前端啊 就算后端写的比较拉也能高逼格)

以下是写完项目后的一些记录,包含一些重要的配置和代码。

11.一些配置

11.1 允许客户端携带验证信息 AddResponseHeaderFilter

因为本项目将使用cookie,因此必须允许客户端携带验证信息。实现的方法是继承重写spring web的OncePerRequestFilter,对每一个申请都在回复中添加请求头Access-Control-Allow-Credentials为ture。

import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class AddResponseHeaderFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { response.addHeader("Access-Control-Allow-Credentials", "true"); filterChain.doFilter(request,response); }
}

11.2 跨域设置 CorsConfig

前后端分离的跨域设置。允许前端的origin向后端发送请求。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter; @Configuration
public class CorsConfig { // 当前跨域请求最大有效时长。这里默认1天
private static final long MAX_AGE = 24 * 60 * 60; private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("http://127.0.0.1:5173"); // 1 设置访问源地址
corsConfiguration.addAllowedOrigin("https://blog-alpha.dev.mxowl.com");
corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头
corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
corsConfiguration.setMaxAge(MAX_AGE);
return corsConfiguration;
} @Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig()); // 4 对接口配置跨域设置
return new CorsFilter(source);
}
}

11.3 MybatisPlus配置 MybatisPlusConfig

该处主要配置了分页插件。后续的博客搜索、评论搜索等都会用到分页查询

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

11.4 其他配置

在application.java中的配置:引入了spring security的加密。

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@SpringBootApplication(exclude= SecurityAutoConfiguration.class)
@MapperScan("com.example.QLblog.mapper")
public class SpringbootTestApplication { public static void main(String[] args) {
SpringApplication.run(SpringbootTestApplication.class, args);
} @Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
} }

我们只要用:

bCryptPasswordEncoder.encode(user.getPassword())

即可加密密码。当然,控制类需装配进来:

@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;

在application.properties中配置:(隐去了敏感信息)

# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # 数据库用户名&密码:
spring.datasource.username=*** #开发版
#spring.datasource.url=jdbc:mysql://***:3306/qlblog?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8
#spring.datasource.password=*** #发行版
spring.datasource.url=jdbc:mysql://localhost:3306/qlblog?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8
spring.datasource.password=***
# 应用服务 WEB 访问端口
server.port=9090 # 文件上传ip
file.ip=*** #文件上传限额
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB #连接sql字符集为utf8mb4 可能是多余的
spring.datasource.hikari.connection-init-sql=set names utf8mb4 collate utf8mb4_unicode_ci
spring.datasource.tomcat.init-s-q-l=set names utf8mb4 collate utf8mb4_unicode_ci

12 Cookie相关

12.1 生成Token的工具

之后生成cookie时,会先将用户信息装进token,再装进cookie。本工具类定义了通过user生成token的方法。

import cn.hutool.core.date.DateUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.liuzhiwen.recruitment.common.Result;
import com.liuzhiwen.recruitment.entity.User;
import com.liuzhiwen.recruitment.mapper.UserMapper;
import com.liuzhiwen.recruitment.entity.User;
import com.liuzhiwen.recruitment.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date; @Slf4j
@Component
public class TokenUtils { @Resource
private UserMapper userMapper; private static UserMapper staticUserMapper; @PostConstruct
public void init() {
staticUserMapper = userMapper;
} /**
* 生成token
* @param user
* @return
*/
public static String genToken(User user) {
return JWT.create().withExpiresAt(DateUtil.offsetDay(new Date(), 1)).withAudience(user.getId().toString())
.sign(Algorithm.HMAC256(user.getPassword()));
} /**
* 获取token中的用户信息
* @return
*/
public static User getUser() {
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader("token");
String aud = JWT.decode(token).getAudience().get(0);
Integer userId = Integer.valueOf(aud);
return staticUserMapper.selectById(userId);
} catch (Exception e) {
log.error("解析token失败", e);
return null;
}
} }

12.2 基础控制类 BaseController

因为有些操作,我们所有的controller都会使用,因此我创建了BaseController用于存放这些会反复用到的、重要的函数。如:

l 通过token获取user

l 通过cookie获取token,进而获取token

l 通过cookie判断用户登陆状态和信息

其他controller只要继承这玩意就行。

*后端的几乎所有敏感操作都会通过cookie进行身份的验证!*

import com.auth0.jwt.JWT;
import com.liuzhiwen.recruitment.entity.User;
import com.liuzhiwen.recruitment.mapper.UserMapper;
import com.liuzhiwen.recruitment.entity.User;
import com.liuzhiwen.recruitment.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@RestController
public class BaseController { @Resource
UserMapper userMapper; @Autowired
protected HttpServletRequest request; /**
* 根据token获取用户信息
* @return user
*/
public User getUserFromToken() {
String token = request.getHeader("token");
String aud = JWT.decode(token).getAudience().get(0);
Integer userId = Integer.valueOf(aud);
return userMapper.selectById(userId);
}
/**
* 根据cookie获得token,获得用户信息
* @return user
*/
public User getUserFromCookie(){
String token = null;
for(int i = 0; i < request.getCookies().length; i++){
if(request.getCookies()[i].getName().equals("token")){
token = request.getCookies()[i].getValue();
}
}
if(token!=null){
String aud = JWT.decode(token).getAudience().get(0);
Integer userId = Integer.valueOf(aud);
if(userMapper.selectById(userId)!=null){
User retUser = userMapper.selectById(userId);
retUser.setPassword("");
return retUser;
}else {
return null;
}
}else {
return null;
} }
/**
* 判断登录状态及用户是否存在
* @return boolean
*/
//验证登陆状态
public boolean verifyLoginStatus(){
if (request.getCookies() == null) {
return false;
}
String token = null;
for (int i = 0; i < request.getCookies().length; i++) {
if (request.getCookies()[i].getName().equals("token")) {
token = request.getCookies()[i].getValue();
}
}
if (token == null) return false;
return getUserFromCookie() != null;
}
/*验证登录模板:
if(!verifyLoginStatus()){
return Result.error("-1","登录状态有误,请重新登陆!");
}
*/
}

12.3 组装cookie及注销

//登录 post /login
@PostMapping("/login")
public Result<?> login(@RequestBody User userParam, HttpServletResponse response) {
User userPwd = userMapper.selectPwdByName(userParam.getUsername());
if(userPwd==null){
return Result.error("-1","用户名错误");
}
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", userParam.getUsername());
queryWrapper.eq("password", userPwd.getPassword());
User res = userMapper.selectOne(queryWrapper); // 判断密码是否正确
if (!bCryptPasswordEncoder.matches(userParam.getPassword(), userPwd.getPassword())) {
return Result.error("-1", "密码错误");
}
if (res == null) {
return Result.error("-1", "用户名或密码错误");
} // 生成token
String token = TokenUtils.genToken(res);
//res.setToken(token); //组装cookie
final ResponseCookie responseCookie = ResponseCookie
.from("token", token)
//.secure(true)
.httpOnly(true)
.path("/")
.maxAge(60 * 60 * 24 * 7) //7天有效期
.sameSite("Lax")
.build();
response.addHeader(HttpHeaders.SET_COOKIE, responseCookie.toString()); res.setPassword(""); return Result.success(res);
} //注销
@PostMapping("/logout")
public Result<?> logOut(HttpServletResponse response){
if(request.getCookies()==null){
return Result.error("-1","未登录");
}
final ResponseCookie responseCookie = ResponseCookie
.from("token", "")
//.secure(true)
.httpOnly(true)
.path("/")
.maxAge(0)
.sameSite("Lax")
.build();
response.addHeader(HttpHeaders.SET_COOKIE, responseCookie.toString()); return Result.success();
}

13 文件上传

13.1 普通文件上传

@RestController
@RequestMapping("/file")
public class FileController extends BaseController {
@Value("${server.port}")
private String port; @Value("${file.ip}")
private String ip; /**
* 上传接口
*
* @param file
* @return
* @throws IOException
*/
@PostMapping("/upload")
public Result<?> upload(MultipartFile file) throws IOException {
if (file == null) {
return Result.error("-1", "文件为空");
} String fileType = file.getContentType();
if(fileType==null){
return Result.error("-1", "未知文件格式");
}
if (!fileType.contains("image/")) {
return Result.error("-1", "文件格式上传错误");
} if(!verifyLoginStatus()){
return Result.error("-1","登录状态有误,请重新登陆!");
} String originalFilename = file.getOriginalFilename(); // 获取源文件的名称
// 定义文件的唯一标识(前缀)
String fileUUID = IdUtil.fastSimpleUUID();
String rootFilePath = System.getProperty("user.dir") + "/files/" + fileUUID + "_" + originalFilename; // 获取上传的路径
File rootFile = new File(rootFilePath);
if (!rootFile.getParentFile().exists()) {
rootFile.getParentFile().mkdirs();
}
FileUtil.writeBytes(file.getBytes(), rootFilePath); // 把文件写入到上传的路径
return Result.success("/file/" + fileUUID); // 返回结果 url
} /**
* 下载接口
*
* @param fileUUID
* @param response
*/
@GetMapping("/{fileUUID}")
public Result<?> getFiles(@PathVariable String fileUUID, HttpServletResponse response) {
OutputStream os; // 新建一个输出流对象
String basePath = System.getProperty("user.dir") + "/files/"; // 定于文件上传的根路径
List<String> fileNames = FileUtil.listFileNames(basePath); // 获取所有的文件名称
String fileName = fileNames.stream().filter(name -> name.contains(fileUUID)).findAny().orElse(""); // 找到跟参数一致的文件
try {
if (StrUtil.isNotEmpty(fileName)) {
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
response.setContentType("application/octet-stream"); response.setHeader("cache-control","max-age=5184000");
byte[] bytes = FileUtil.readBytes(basePath + fileName); // 通过文件的路径读取文件字节流
os = response.getOutputStream(); // 通过输出流返回文件
os.write(bytes);
os.flush();
os.close();
}
} catch (Exception e) {
return Result.error("-1","文件下载失败");
}
return Result.success();
} }

13.2 头像上传

此类上传需记录url到数据库

 @PostMapping("/upload_avatar")
public Result<?> upload(MultipartFile file) throws IOException {
if (file == null) {
return Result.error("-1", "文件为空");
} String fileType = file.getContentType();
if (fileType == null) {
return Result.error("-1", "未知文件格式");
}
if (!fileType.contains("image/")) {
return Result.error("-1", "文件格式上传错误");
} if (!verifyLoginStatus()) {
return Result.error("-1", "登录状态有误,请重新登陆!");
} String originalFilename = file.getOriginalFilename(); // 获取源文件的名称
// 定义文件的唯一标识(前缀)
String fileUUID = IdUtil.fastSimpleUUID();
String rootFilePath = System.getProperty("user.dir") + "/files/" + fileUUID + "_" + originalFilename; // 获取上传的路径
File rootFile = new File(rootFilePath);
if (!rootFile.getParentFile().exists()) {
rootFile.getParentFile().mkdirs();
}
FileUtil.writeBytes(file.getBytes(), rootFilePath); // 把文件写入到上传的路径 User user = getUserFromCookie();
Profile profile = profileMapper.selectById(user.getId());
profile.setAvatar("/file/" + fileUUID);
profileMapper.updateById(profile); return Result.success("/file/" + fileUUID); // 返回结果 url }

14 运行配置configuration

14.1 Application

@SpringBootApplication(exclude= SecurityAutoConfiguration.class)
@MapperScan("com.example.QLblog.mapper")
public class SpringbootTestApplication { public static void main(String[] args) {
SpringApplication.run(SpringbootTestApplication.class, args);
} @Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
} }

14.2 Edit configurations

15 2024/1/8更新

15.1 Service的加入

这次增加了service(但是没有ServiceImply)所以直接把service类写成class就行,然后把controller里的逻辑搬到service里就行,controller只负责路由。如图所示:

需要注意的是因为业务逻辑的抽离,basecontroller不再有用,将其中的函数搬到baseservice里,由service继承。

15.2 数据库与实体类命名相关

这个之前忘说了。数据库两个单词要用下划线隔开,而实体类用小驼峰。实体类用下划线的话搜不到的。

如:数据库中表项:user_name 实体类就要: userName

等毕设做的差不多了会将本文重制为更完善的模板。现在就暂时缝缝补补

自用的springboot后端增删改查模板的更多相关文章

  1. spring--boot数据库增删改查

    spring--boot数据库增删改查 数据库配置:(必须配置),我写的文件是yml的,和properties是相同的 1 spring: 2 datasource: 3 driver-class-n ...

  2. 【Mybatis】简单的mybatis增删改查模板

    简单的mybatis增删改查模板: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE map ...

  3. SpringBoot+Mybatis增删改查实战

    简介 SpringBoot和Mybatis是啥请自行百度,作者这里也是花了几天时间入门了这个框架用来完成任务,并且也算符合要求的完成了任务,期间也各种百度但是没找到自己想要的那种简单易懂的教程,所以踩 ...

  4. MyBatis增删改查模板

    1. 首先,和Spring整合一下 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns=& ...

  5. springboot&mybatis 增删改查系列(二)

    数据库篇 我的数据库名为data0525,数据表名为user,其中有五列uid,uname,upass,usex,umessage.uid为主键并且自动生成,由于是练习表,所以并没有考虑设计的合理性. ...

  6. 使用IDEA搭建SpringBoot进行增删改查

    功能环境:java1.8以上  .IntellJIDEA  First: 创建项目,请根据项目图一步一步完成建立. 二.配置数据库 三.创建实体对象建表或对应存在表,根据需要加入相应注解 四.创建应用 ...

  7. springboot&mybatis 增删改查系列(一)

    创建父项目 首先,我们需要创建一个Maven项目. 在这个项目的pom文件中加入以下几个依赖: <!-- spring boot --> <parent> <groupI ...

  8. SpringBoot JPA + H2增删改查示例

    下面的例子是基于SpringBoot JPA以及H2数据库来实现的,下面就开始搭建项目吧. 首先看下项目的整体结构: 具体操作步骤: 打开IDEA,创建一个新的Spring Initializr项目, ...

  9. Django中ORM对数据库的增删改查

    Django中ORM对数据库数据的增删改查 模板语言 {% for line in press %} {% line.name %} {% endfor %} {% if 条件 %}{% else % ...

  10. springboot+layui实现PC端用户的增删改查 & 整合mui实现app端的自动登录和用户的上拉加载 & HBuilder打包app并在手机端下载安装

    springboot整合web开发的各个组件在前面已经有详细的介绍,下面是用springboot整合layui实现了基本的增删改查. 同时在学习mui开发app,也就用mui实现了一个简单的自动登录和 ...

随机推荐

  1. Mono GC

    1.虽然是stw但mark阶段可以concurrent 2.并行mark就需要写屏障 3.unity的gc也不是扫描整个堆内存 https://schani.wordpress.com/2012/12 ...

  2. 牛逼,这款开源聊天应用竟能一键召唤多个AI助手,跨平台通话神器!

    嗨,大家好,我是小华同学,关注我们获得"最新.最全.最优质"开源项目和高效工作学习方法 JiwuChat是一款基于Tauri2和Nuxt3构建的轻量化多平台即时通讯工具,仅约8MB ...

  3. Nacos源码—2.Nacos服务注册发现分析二

    大纲 5.服务发现-服务之间的调用请求链路分析 6.服务端如何维护不健康的微服务实例 7.服务下线时涉及的处理 8.服务注册发现总结 5.服务发现-服务之间的调用请求链路分析 (1)微服务通过Naco ...

  4. uni-app小程序登录后…

    前情 最近新接了一个全新项目,是类似商城的小程序项目,我负责从0开始搭建小程序,我选用的技术栈是uni-app技术栈,其中就有一个用户登录功能,小程序部分页面是需要登录才可以查看的,对于未登录的用户需 ...

  5. C# AggreateException

    在 C# 中,AggregateException 是一种特殊类型的异常,它允许在多个异步任务中捕获并组合多个异常.当在一个异步任务中同时执行多个子任务时,如果其中任何一个子任务抛出了异常,那么父任务 ...

  6. 自签名证书工具cfssl详解

    概述 GitHub地址:https://github.com/cloudflare/cfssl 官方地址:https://pkg.cfssl.org CFSSL(CloudFlare's PKI an ...

  7. Redis集群的三种姿势

    一.Redis主从复制 主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主. 1.如何实现 新建三个配置文件,分别命名为redis_ ...

  8. vivo Pulsar 万亿级消息处理实践(2)-从0到1建设 Pulsar 指标监控链路

    作者:vivo 互联网大数据团队- You Shuo 本文是<vivo Pulsar万亿级消息处理实践>系列文章第2篇,Pulsar支持上报分区粒度指标,Kafka则没有分区粒度的指标,所 ...

  9. 特殊恢复:最简单的BBED修改ASM的数据块的方法

    我们的文章会在微信公众号Oracle恢复实录和博客网站同步更新 ,欢迎关注收藏,也欢迎大家转载,但是请在文章开始地方标注文章出处,谢谢! 由于博客中有大量代码,通过页面浏览效果更佳. 前天在客户现场遇 ...

  10. go 进阶训练营 微服务可用性(中)笔记

    过载保护 令牌桶算法 存放固定容量令牌的桶,按照固定速率往桶里添加令牌 https://pkg.go.dev/golang.org/x/time/rate 漏桶算法 作为计量工具(The Leaky ...