bash
/**
* 地址簿管理
*/
@Slf4j
@RestController
@RequestMapping("/addressBook")
public class AddressBookController {
@Autowired
private AddressBookService addressBookService;
/**
* 新增
*/
@PostMapping
public R<AddressBook> save(@RequestBody AddressBook addressBook) {
//使用前面封装的localThread来保持这个数据
addressBook.setUserId(BaseContext.getCurrentId());
log.info("addressBook:{}", addressBook);
addressBookService.save(addressBook);
return R.success(addressBook);
}
/**
* 根据地址id删除用户地址
* @param id
* @return
*/
@DeleteMapping
public R<String> delete(@RequestParam("ids") Long id){
if (id == null){
return R.error("请求异常");
}
LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AddressBook::getId,id).eq(AddressBook::getUserId,BaseContext.getCurrentId());
addressBookService.remove(queryWrapper);
//addressBookService.removeById(id); 感觉直接使用这个removeById不太严谨.....
return R.success("删除地址成功");
}
/**
* 修改收货地址
* @param addressBook
* @return
*/
@PutMapping
public R<String> update(@RequestBody AddressBook addressBook){
if (addressBook == null){
return R.error("请求异常");
}
addressBookService.updateById(addressBook);
return R.success("修改成功");
}
/**
* 设置默认地址
* 1表示默认地址
*/
@PutMapping("default")
public R<AddressBook> setDefault(@RequestBody AddressBook addressBook) {
log.info("addressBook:{}", addressBook);
LambdaUpdateWrapper<AddressBook> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
wrapper.set(AddressBook::getIsDefault, 0);
//SQL:update address_book set is_default = 0 where user_id = ?
addressBookService.update(wrapper);
addressBook.setIsDefault(1);
//SQL:update address_book set is_default = 1 where id = ?
addressBookService.updateById(addressBook);
return R.success(addressBook);
}
/**
* 根据id查询地址
*/
@GetMapping("/{id}")
public R get(@PathVariable Long id) {
AddressBook addressBook = addressBookService.getById(id);
if (addressBook != null) {
return R.success(addressBook);
} else {
return R.error("没有找到该对象");
}
}
/**
* 查询默认地址
*/
@GetMapping("default")
public R<AddressBook> getDefault() {
LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId());
queryWrapper.eq(AddressBook::getIsDefault, 1);
//SQL:select * from address_book where user_id = ? and is_default = 1
AddressBook addressBook = addressBookService.getOne(queryWrapper);
if (null == addressBook) {
return R.error("没有找到该对象");
} else {
return R.success(addressBook);
}
}
/**
* 查询指定用户的全部地址
*/
@GetMapping("/list")
public R<List<AddressBook>> list(AddressBook addressBook) {
addressBook.setUserId(BaseContext.getCurrentId());
log.info("addressBook:{}", addressBook);
//条件构造器
LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(null != addressBook.getUserId(), AddressBook::getUserId, addressBook.getUserId());
queryWrapper.orderByDesc(AddressBook::getUpdateTime);
//SQL:select * from address_book where user_id = ? order by update_time desc
return R.success(addressBookService.list(queryWrapper));
}
}
@RequestMapping
, @GetMapping
, 和 @PostMapping
是 Spring MVC 框架中用于映射 HTTP 请求到特定处理器方法的注解。它们之间有一些重要的区别,主要体现在用途和使用方式上。
@RequestMapping
- 通用性:这是最通用的映射注解,可以用来处理所有类型的HTTP请求(GET, POST, PUT, DELETE等)。它允许你指定URL模式、HTTP方法、请求参数、头部信息等条件来细化匹配规则。
- 灵活性:可以在类级别或方法级别使用。在类级别使用时,它为该类中的所有方法提供了一个基础路径;在方法级别使用时,它可以进一步限定具体的方法应该响应哪些请求。
- 配置复杂度 :由于其通用性,
@RequestMapping
提供了更多的配置选项,这也意味着它的使用可能更加复杂一些。
示例
java
@RequestMapping(value = "/users", method = RequestMethod.GET)
public String listUsers() {
// 处理获取用户列表的逻辑
}
@GetMapping
- 简化版 :这是
@RequestMapping
的一个特化版本,专门用于映射HTTP GET请求。它简化了GET请求的映射配置,不需要显式地设置method属性。 - 清晰度 :使用
@GetMapping
可以使代码更具有可读性和表达力,因为它的意图非常明确------只处理GET请求。 - 默认行为 :除了指定路径外,默认情况下它会自动匹配GET请求,并且支持通过
params
和headers
等属性进行进一步的条件限制。
示例
java
@GetMapping("/users")
public String listUsers() {
// 处理获取用户列表的逻辑
}
@PostMapping
- 简化版 :与
@GetMapping
类似,@PostMapping
是@RequestMapping
针对POST请求的特化版本。它简化了POST请求的映射配置。 - 用途:通常用于处理表单提交、文件上传或其他需要创建资源的操作。这些操作一般不会导致浏览器刷新页面,而是发送数据到服务器以供处理。
- 清晰度:同样提高了代码的可读性和意图表达,明确指出此方法只处理POST请求。
示例
java
@PostMapping("/users")
public String createUser(@RequestBody User user) {
// 处理创建新用户的逻辑
}
总结
- 如果你只需要映射一种HTTP方法,如GET或POST,那么使用
@GetMapping
或@PostMapping
会使你的代码更简洁明了。 - 如果你需要映射多种HTTP方法或者需要更复杂的条件来确定哪个方法应该响应请求,那么
@RequestMapping
提供了更大的灵活性。 - 选择哪个注解取决于具体的业务需求以及你希望代码有多清晰易懂。在大多数情况下,优先考虑使用
@GetMapping
和@PostMapping
,除非有特殊原因需要使用@RequestMapping
。
@RequestMapping("/addressBook")
是一个Spring MVC框架中的注解,它用于映射Web请求到控制器类或特定的处理器方法。在这个上下文中,@RequestMapping("/addressBook")
被应用在类级别上,意味着这个类中所有处理HTTP请求的方法默认都会基于/addressBook
路径。
详细解释
-
位置 :该注解放置在控制器类(如
AddressBookController
)之上。 -
作用:
- 它指定了该控制器处理的所有URL的基础路径为
/addressBook
。这意味着任何定义在这个控制器里的方法如果想要处理来自客户端的HTTP请求,那么这些请求的URL必须以/addressBook
开头。 - 如果方法级别的@RequestMapping没有指定额外的路径,则它们会直接继承类级别的路径;如果有指定,则是类级别路径加上方法级别路径的组合。
- 它指定了该控制器处理的所有URL的基础路径为
-
示例:
- 对于
@PostMapping
注解的方法,如果没有额外指定路径,默认情况下它将处理POST /addressBook
的请求。 - 如果有方法使用了
@GetMapping("/list")
,那么它将处理GET /addressBook/list
的请求。
- 对于
-
灵活性:
- 可以通过在方法上添加更具体的@RequestMapping注解来进一步细化路由规则。例如,可以为不同的HTTP方法(GET, POST, PUT, DELETE等)提供不同的端点,或者根据请求参数、头部信息等条件来匹配请求。
-
与@RestController结合:
- 当
@RequestMapping
和@RestController
一起使用时,它不仅限定了URL路径,还确保了返回的对象会被自动转换成JSON格式作为HTTP响应体的一部分返回给客户端。
- 当
示例代码说明
java
@Slf4j
@RestController
@RequestMapping("/addressBook")
public class AddressBookController {
@Autowired
private AddressBookService addressBookService;
/**
* 新增地址簿条目
*/
@PostMapping
public R<AddressBook> save(@RequestBody AddressBook addressBook) {
// 设置当前用户的ID
addressBook.setUserId(BaseContext.getCurrentId());
log.info("addressBook:{}", addressBook);
addressBookService.save(addressBook);
return R.success(addressBook);
}
}
在这个例子中:
@RequestMapping("/addressBook")
指定AddressBookController
处理的所有请求都将以/addressBook
为前缀。@PostMapping
方法save
处理的是POST /addressBook
的请求,因为这里没有指定额外的路径。- 如果需要处理其他类型的请求或不同的路径,可以在每个方法上添加更详细的@RequestMapping注解,例如
@GetMapping("/list")
表示处理GET /addressBook/list
请求。
总之,@RequestMapping("/addressBook")
使得所有的API端点都有一个统一的入口点,这有助于组织和管理应用程序的路由结构,并使URL设计更加清晰和一致。
这段代码定义了一个处理HTTP DELETE请求的方法,用于删除地址簿条目。它使用了@DeleteMapping
注解来指定这是一个DELETE请求的处理器,并且通过检查和验证请求参数以及用户身份来确保安全性和准确性。下面是对这段代码的详细解释:
方法签名
java
@DeleteMapping
public R<String> delete(@RequestParam("ids") Long id) {
-
注解:
@DeleteMapping
:指明这是一个用来处理HTTP DELETE请求的方法。
-
返回类型 :
R<String>
,这里假设R
是一个通用响应封装类,用于封装API的响应信息,包括状态、消息和数据。 -
方法参数 :
@RequestParam("ids") Long id
,表示从URL查询参数中获取名为ids
的长整型值作为要删除的地址簿条目的ID。注意这里的参数名是ids
,但实际接收的是单个Long
类型的id
,这可能是个命名不一致的问题,应该根据实际情况调整为id
或接受一个列表List<Long>
以支持批量删除。
参数验证
java
if (id == null){
return R.error("请求异常");
}
- 检查传入的
id
是否为空。如果为空,则立即返回一个错误响应,告知客户端请求无效。
构建查询条件
java
LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AddressBook::getId, id).eq(AddressBook::getUserId, BaseContext.getCurrentId());
- 使用MyBatis-Plus提供的
LambdaQueryWrapper
构建查询条件。 queryWrapper.eq(AddressBook::getId, id)
:确保只删除ID匹配的地址簿条目。queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId())
:进一步限制删除操作只能影响当前登录用户的地址簿条目。BaseContext.getCurrentId()
应该是一个线程局部变量(ThreadLocal),用于保存当前用户的ID。
执行删除操作
java
addressBookService.remove(queryWrapper);
// addressBookService.removeById(id); // 感觉直接使用这个removeById不太严谨.....
- 调用
addressBookService.remove(queryWrapper)
执行删除操作,该方法会根据queryWrapper
中设定的条件查找并删除符合条件的记录。 - 注释掉的
addressBookService.removeById(id)
方法虽然可以直接根据ID删除记录,但它缺乏对用户身份的校验,可能导致安全性问题,如不同用户之间的数据泄露或误删他人数据。因此,采用带有用户ID校验的方式更为严谨。
返回结果
java
return R.success("删除地址成功");
- 如果删除操作顺利完成,则返回一个成功的响应给客户端,包含一条确认消息"删除地址成功"。
总结
这段代码展示了如何安全地处理删除请求,特别是当涉及到多租户或多用户系统时,确保每个用户只能修改自己的数据是非常重要的。通过添加额外的查询条件(如用户ID)可以有效地防止跨用户的数据篡改。此外,代码还展示了良好的实践,比如在进行数据库操作之前先验证输入参数的有效性。
这段代码定义了一个处理HTTP POST请求的方法,用于创建新的地址簿条目。它使用了@PostMapping
注解来指定这是一个POST请求的处理器,并通过设置用户ID和日志记录确保操作的安全性和可追溯性。下面是对这段代码的详细解释:
方法签名
java
@PostMapping
public R<AddressBook> save(@RequestBody AddressBook addressBook) {
-
注解:
@PostMapping
:指明这是一个用来处理HTTP POST请求的方法,通常用于提交表单或上传数据。
-
返回类型 :
R<AddressBook>
,这里假设R
是一个通用响应封装类,用于封装API的响应信息,包括状态、消息和数据。在这个例子中,它会包含一个新创建的AddressBook
对象。 -
方法参数 :
@RequestBody AddressBook addressBook
,表示从HTTP请求体中反序列化出一个AddressBook
对象作为方法参数。这意味着客户端发送的数据(如JSON格式)将被转换成Java对象供服务器端使用。
设置用户ID
java
addressBook.setUserId(BaseContext.getCurrentId());
- 这行代码的作用是为传入的
addressBook
对象设置userId
属性。 BaseContext.getCurrentId()
应该是一个线程局部变量(ThreadLocal),用于保存当前用户的ID。这种方法常用于在多租户或多用户系统中保持每个请求的上下文信息,以确保每个用户的操作只能影响他们自己的数据。- 通过这种方式,即使客户端没有提供用户ID,系统也可以自动关联到正确的用户,从而增强了安全性。
日志记录
java
log.info("addressBook:{}", addressBook);
- 使用
log.info(...)
记录下即将保存的addressBook
对象的信息。这对于调试和监控非常有用,可以跟踪哪些数据正在被插入数据库。 {}
是占位符,会被传入的对象替换,这里是addressBook
对象,通常会被转换成字符串形式的日志输出。
执行保存操作
java
addressBookService.save(addressBook);
- 调用
addressBookService.save(addressBook)
执行实际的保存逻辑。addressBookService
应该是实现了对AddressBook
实体进行CRUD操作的服务层接口。 - 具体实现细节取决于服务层如何与数据库交互,但通常这一步会将
addressBook
对象持久化到数据库中。
返回结果
java
return R.success(addressBook);
- 如果保存操作顺利完成,则返回一个成功的响应给客户端。这个响应包含了状态信息(成功)、可能的消息(如"保存成功")以及刚刚保存的
addressBook
对象本身。 - 客户端可以根据返回的
addressBook
对象获取新创建资源的信息,例如自动生成的ID等。
总结
这段代码展示了如何安全地处理创建新资源的POST请求,特别是当涉及到多租户或多用户系统时,确保每个用户只能创建属于自己的数据是非常重要的。通过添加额外的逻辑(如设置用户ID)可以有效地防止跨用户的数据篡改。此外,代码还展示了良好的实践,比如在进行数据库操作之前先验证输入参数的有效性(虽然在这个例子中没有显示具体的验证逻辑)。日志记录也帮助开发者更好地理解和调试应用程序的行为。
这段代码定义了一个处理HTTP DELETE请求的方法,用于删除地址簿条目。它使用了@DeleteMapping
注解来指定这是一个DELETE请求的处理器,并通过检查和验证请求参数以及用户身份来确保操作的安全性和准确性。下面是对这段代码的详细解释:
方法签名
java
@DeleteMapping
public R<String> delete(@RequestParam("ids") Long id) {
-
注解:
@DeleteMapping
:指明这是一个用来处理HTTP DELETE请求的方法,通常用于删除资源。
-
返回类型 :
R<String>
,这里假设R
是一个通用响应封装类,用于封装API的响应信息,包括状态、消息和数据。在这个例子中,它会包含一条字符串消息。 -
方法参数 :
@RequestParam("ids") Long id
,表示从URL查询参数中获取名为ids
的长整型值作为要删除的地址簿条目的ID。需要注意的是,参数名是ids
,但实际接收的是单个Long
类型的id
,这可能是个命名不一致的问题,应该根据实际情况调整为id
或接受一个列表List<Long>
以支持批量删除。
参数验证
java
if (id == null){
return R.error("请求异常");
}
- 检查传入的
id
是否为空。如果为空,则立即返回一个错误响应给客户端,告知请求无效。
构建查询条件
java
LambdaQueryWrapper<AddressBook> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AddressBook::getId, id).eq(AddressBook::getUserId, BaseContext.getCurrentId());
- 使用MyBatis-Plus提供的
LambdaQueryWrapper
构建查询条件。queryWrapper.eq(AddressBook::getId, id)
:确保只删除ID匹配的地址簿条目。queryWrapper.eq(AddressBook::getUserId, BaseContext.getCurrentId())
:进一步限制删除操作只能影响当前登录用户的地址簿条目。BaseContext.getCurrentId()
应该是一个线程局部变量(ThreadLocal),用于保存当前用户的ID。这种做法可以防止跨用户的数据篡改,增加了安全性。
执行删除操作
java
addressBookService.remove(queryWrapper);
// addressBookService.removeById(id); // 感觉直接使用这个removeById不太严谨.....
- 调用
addressBookService.remove(queryWrapper)
执行删除操作,该方法会根据queryWrapper
中设定的条件查找并删除符合条件的记录。 - 注释掉的
addressBookService.removeById(id)
方法虽然可以直接根据ID删除记录,但它缺乏对用户身份的校验,可能导致安全性问题,如不同用户之间的数据泄露或误删他人数据。因此,采用带有用户ID校验的方式更为严谨。
返回结果
java
return R.success("删除地址成功");
- 如果删除操作顺利完成,则返回一个成功的响应给客户端,包含一条确认消息"删除地址成功"。
总结
这段代码展示了如何安全地处理删除请求,特别是当涉及到多租户或多用户系统时,确保每个用户只能修改自己的数据是非常重要的。通过添加额外的查询条件(如用户ID)可以有效地防止跨用户的数据篡改。此外,代码还展示了良好的实践,比如在进行数据库操作之前先验证输入参数的有效性。通过这种方式,不仅提高了系统的安全性,也增强了用户体验。
HTTP PUT 和 HTTP GET 请求是两种不同的HTTP方法,它们在用途、安全性、幂等性和缓存等方面存在显著差异。下面是两者的主要区别:
1. 用途
-
GET:
- 用于从服务器获取资源。它主要用于检索信息,不应该用于修改服务器上的数据。
- 示例:
GET /users/123
用于获取用户ID为123的用户信息。
-
PUT:
- 用于向指定资源位置上传其最新状态。通常用来更新现有资源或创建一个新资源(如果该资源不存在)。
- 示例:
PUT /users/123
可以用来更新用户ID为123的用户信息。
2. 幂等性
-
GET:
- GET请求是幂等的,意味着无论执行多少次相同的GET请求,其结果应该相同,并且不会改变服务器端的状态。
-
PUT:
- PUT请求也是幂等的。多次执行相同的PUT请求应当产生同样的效果,即最终资源的状态应该一致。例如,如果你发送多个相同的PUT请求去更新某个资源,这个资源只会被更新到最后一次请求所指定的状态。
3. 安全性
-
GET:
- GET请求被认为是安全的(idempotent and safe),因为它不应用于改变服务器状态。因此,不应该通过GET请求来传递敏感数据,如密码或信用卡号码。
-
PUT:
- PUT请求不是安全的,因为它可能会改变服务器上的数据。因此,使用PUT时需要确保有足够的权限验证和安全措施,以防止未授权的数据修改。
4. 缓存
-
GET:
- GET请求可以被浏览器和其他中间件(如代理服务器)缓存。这意味着后续对同一资源的GET请求可能会返回缓存的数据,而不必每次都访问服务器。
-
PUT:
- PUT请求通常不会被缓存,因为每次PUT请求都可能携带新的数据,目的是更新服务器上的资源。
5. 参数传递方式
-
GET:
- 参数通过URL查询字符串传递,如
?key=value
。这种方式使得参数可见,容易被记录在日志中或书签保存,不适合传递敏感信息。
- 参数通过URL查询字符串传递,如
-
PUT:
- 参数通常是作为请求体的一部分发送给服务器,这使得它们不在URL中显示,更加安全。但是,这也意味着PUT请求不能直接通过浏览器地址栏发起。
6. 副作用
-
GET:
- 不应有副作用,即不应修改服务器上的任何资源。
-
PUT:
- 可能会有副作用,因为它涉及到资源的创建或更新。
总结
选择使用GET还是PUT取决于你想要完成的操作类型。GET适用于读取数据,而PUT则用于更新或创建资源。理解这两者的区别对于构建RESTful API非常重要,有助于确保API的安全性、可靠性和性能。