新蜂商城(newbee-mall-api)部分接口实验,跨域处理(同源策略,跨域访问,CORS),系统安全问题(Session,Cookie,Token,OAuth)(软件工程综合实践课程第十三周)

文章目录

一、要求

各小组对前后端分离“新蜂商城”项目的后端“ newbee-mall-api-master”进行学习,具体要求如下;

  • 学习项目中 “admin”包下的各接口;
  • 每个小组成员负责2个 Api 对应的“实体类–mapper接口和mapper.xml–service接口和实现类”等相关类的学习,并在实验报告中写出这些类、接口的主要功能;
  • 对自己负责的 API 进行接口测试,并截图,粘贴到实验报告中

二、知识总结

跨域处理

1、同源策略

1.1 浏览器的同源策略

浏览器同源策略是一种约定,是最核心、也是最基本的安全功能。它由Netscape最早提出,要求浏览器把 相同的协议名、域名( ( 或二级域名) ) 和端口号的 URL 识别为同源网站,反之则是非同源网站。只有同源的网站才可以进行 Ajax 和 Cookie 的操作,而对超链接、JavaScript和 CSS 等的引用则不需要同源限制。当前,所有支持 JavaScript的浏览器都会使用这个策略。下图举例说明了跨域原因。
在这里插入图片描述
如果 域名和端口都相同,但是请求路径不同,不属于跨域,如:
www.jd.com/i tem 和 www.jd.com/goods 属于同源。
因此,同源策略要求,在浏览器再次发出URL请求时,必须与浏览器当前的URL地址处于同域上,也就是域名、端口和协议都要相同,如果不同,就会报跨域错误,同时浏览器对非同源请求的返回结果会进行拦截。

1.2 跨域请求实验

在这里插入图片描述

2 跨域访问

**当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。**在实际项目开发中,由于各种原因经常有跨域的需求,如做单点登录,在A网站登录后,跳转到B网站时不需要再输入用户名密码。 前后端分离开发模式下,前端和后端分别部署在不同URL的服务器上,跨域访问问题尤其突出。如果不解决跨域问题,前端将不能成功的访问后端API。
目前解决跨域请求问题,可以使用 JSONP(传统的解决方案,只能使用GET请求),反向代理(如nginx,在前端服务器解决),添加响应头处理跨域(如CORS,在后端服务器解决)等多种技术。其中 CORS方式是最常用的跨域实现方式,而且是对各种请求方法、各种数据请求类型都支持。

2.1 添加响应头来处理跨域
2.1.1 CORS 跨域资源共享

跨域资源共享( CORS , Cross- - origin resource sharing) 是由 W3C 推出的解决非同源网站进行交互的一种机制。它允许浏览器和服务器交互 HTTP 头部信息,来决定跨域请求是否有权限。CORS需要浏览器和服务器同时支持。目前所有主流浏览器都支持该功能,但是IE浏览器不能低于IE10。

CORS机制的特点:

  • 浏览器自动完成,不需要用户参与;
  • 前端进行正常的Ajax请求;
  • 浏览器一旦发现Ajax有跨域的请求,就会 自动添加头部信息;
  • 在服务器端 需要设置允许跨域的 HTTP 请求头,实现跨域;
  • 服务器接收浏览器发送请求的头部信息,并根据这些信息,对是否允许跨域请
    求做出应答。
    可以看出,实现CORS 通信的关键是服务器端的设置
2.1.2 设置 HTTP 响应头信息,跨域请求成功

(1)跨域请求分类
CORS标准中,浏览器将跨域请求分为简单请求和非简单请求,其处理方案略有差异。

  • 简单请求:同时满足以下条件,就属于简单请求。
    (1) 请求方法是以下三种方法之一:
    HEAD , GET , POST
    (2)HTTP的头信息不超出以下几种字段:
    Accept , Accept-Language , Content-Language , Content-Type
    另外,请求数据的编码格式:只能是 application/x-www-form-urlencoded、
    multipart/form-data、text/plain
    三个值中的一个。
  • 非简单请求:不满足以上条件,就属于非简单请求。

(2)跨域请求处理:

  • 处理简单请求流程:
    浏览器直接发出CORS请求,自动在请求头部信息中加上 Origin字段,并告诉服
    务器这个请求来自哪个源( 请求协议+ 域名+ 端口),如下图所示;
    在这里插入图片描述
  • 服务器收到请求后,会检查这个字段,如果该字段值不在服务器的许可范围
    内,则会返回正常的HTTP响应,但是其相应的头部信息中不会包含
    “ Access- - Control- - Allow- - Origin”字段值,浏览器发现后,就会抛出异常,
    提示响应的头部信息中没有这个字段。
  • 如果Origin字段值在服务器的许可范围内,则在服务器的响应头部信息中会
    加上以下字段:
    ◆ Access-Control-Allow-Origin: URL 地址—必需项,其值为请求头部信息中Origin的值;
    ◆ Access-Control-Allow-Credentials:true—可选项,布尔值,表示是
    否允许浏览器发送Cookie,需要在应用程序中进行配置;
    ◆ Access-Control-Allow-Headers----浏览器可以从跨域请求响应头部信息中获取的属性值,由服务器配置。默认可以获取Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma 等。
    在这里插入图片描述
  • 处理非简单请求流程 (了解)
    对非简单跨域请求,浏览器的请求处理分为两步:
    ⚫ 预检请求:询问服务器是否允许当前源的访问;
    浏览器请求方式:OPTIONS, 表示这个请求是用来询问的;
    头部信息包含:
    Origin:请求源
    Access-Control-Request-Method: 请求的方法
    Access-Control-Request-Headers:额外的头部信息
    一个非简单请求代码片段:
OPTIONS /cors HTTP/1.1
Origin: http://manage.leyou.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.leyou.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

服务器收到预检信息后,会检查以上3个字段值,以确定是否允许跨域请求,如果
有一项不满足要求,则不允许进行跨域请求。
⚫ 实际请求:如果预检通过,执行实际请求。

3 Springboot 中CORS 机制的应用

在Springboot中,CORS的应用非常简单,可以使用@CrossOrigin注解或配置CORS全局跨域配置类。

3.1 @CrossOrigin 注解

Spring 从4.2版本后开始支持 @CrossOrigin 注解实现跨域,这在一定程度上简化
了跨域访问的实现,在需要跨域访问的方法或者类上加上这个注解即可。
实验步骤:
(1)打开provider项目UseController中selectUsers方法,该方法上配置了@CrossOrigin注解。该注解的配置表示,允许来自http://localhost:8088的跨域请求。
在这里插入图片描述
(2)重新启动provider项目,再次单击index页面上的“测试跨域请求”,发现可以成功访问了。
在这里插入图片描述⚫ 打开“开发者工具”查看请求头信息,可以看到多了一个允许访问的域。
在这里插入图片描述

3.2 全局配置 CORS类

@CrossOrigin 注解可以用在方法上,也可以用在Controller上,但如果每一个方法或控制器类上都加上该注解很繁琐。实际中一般使用全局配置CORS类,即编写一个配置类并实现WebMvcConfigurer接口,然后重写addCorsMappings方法即可,具体参见
provider项目。
在这里插入图片描述
使用该类后,可测试跨域访问成功。其中,ProviderMvcConfig全局配置类实现了WebMvcConfigurer接口,并且重写了addCorsMappings方法,实现了跨域请求配置,其
中常用参数如下:

  • addMapping :配置项目中可以进行跨域请求处理的路径,可以任意配置,可以
    具体到直接请求路径;
  • allowedOrigins :允许哪些URL可以访问本项目的跨域资源,可以固定单条或者
    多条内容,如: allowedOrigins(“*”) , 代表允许任意路径 ;
  • allowedMethods: :设置允许哪些请求方法类型,可以访问该跨域资源,如:POST、
    GET、PUT、OPTIONS、DELETE等;
  • allowedHeaders :允许所有的请求header访问,可以自定义设置任意请求头信
    息,如:“X-YYYY-TOKEN”;
  • allowCredentials:是否允许请求带有验证信息,用户是否可以发送、处理
    cookie;
  • max Age(3600) :设置允许访问的时间。

4 模拟反向代理 ,处理跨域问题

反向代理是相对传统的代理(正向代理)而言的。正向代理是指当客户端无法直接访问目标服务器的资源时,需要在客户端配置代理服务器,客户端可以通过代理服务器提供的服务访问目标服务器的资源。
在这里插入图片描述
反向代理指代理服务器配置在目标服务器端,当用户发来请求时,先由服务器端的代理服务器接收,处理后发送到目标服务器。
在这里插入图片描述
◼ 实验步骤:
(1) 在配置文件中,配置最终要访问的服务器的URL
在这里插入图片描述
(2)编写代理类:在代理类中,接收页面的请求,并将URL地址转换为provider服务的URL地址,同时发起对它的请求。

在这里插入图片描述
(3)页面中直接访问customer服务器
在这里插入图片描述
(4)启动两个项目,访问:http://localhost:8088/proxy,跨域访问成功
在这里插入图片描述
实际开发中,一般使用Nginx服务器在前端服务器上配置反向代理。

系统安全

1 系统安全框架

1.1 认证与授权

软件应用系统设计与开发过程中,必须要考虑系统的安全问题。软件应用系统安全主要包括认证(登录)和授权(权限管理)两部分,一般称为权限管理。

如果不借助第三方安全框架,开发者需要自己设计并实现认证和授权功能。一般的设计思路是:用户在登录页面输入用户名和密码进行登录操作,后端应用程序在收到页面传来的用户名和密码后,就去数据库中进行数据比对,来完成认证工作。登录成功后,还需要查询该用户的角色,再通过角色关联相应的权限,根据权限来判断该用户是否能
进行某项操作。

如果是 Web 项目,一般将用户及对应的角色、权限等信息放到 Session 会话中保存,这样当后端应用每接收一次请求,就可以使用拦截器或过滤器进行判断,判断该会话中是否保存用户的已登录信息。如果是登录状态,则再判断是否有权限访问相应的资源。

在以上过程中,开发者需要自行设计登录的处理、密码加密、Session 的维护、过滤器的设置、权限验证、权限的粒度划分等,开发任务比较繁琐。

当前,有一些优秀的第三方安全框架,它们将软件系统的安全认证相关的功能抽取出来,实现用户身份认证、权限管理、加密(密码加密登录)、会话管理等功能,形成了一个通用的安全认证框架,帮助开发者解决这些通用性问题,让开发者更能专注于自身的业务逻辑。

1.2 常用安全框架

安全框架主要解决应用系统中的两类问题:认证和授权。

  • 认证就是登录,即判断该用户是否是系统中的合法用户;
  • 授权就是权限设计和验证,即判断该用户是否具备访问系统中某些资源的权限。
    当前,Spring Security 和 Shiro 是两个优秀的 Java 安全框架:
  • Spring Security:Spring 家族的一员,是一个能够为基于 Spring 的企业应用
    系统提供声明式的安全访问控制解决方案。它利用了 Spring IOC 和 AOP 功能,
    为应用系统提供声明式访问控制功能,减少了为企业安全控制编写大量重复代码的工作;
  • Shiro 是由 Apache 软件基金会出品的 Java 安全管理框架,具有简单、功能强大的特点,在 Java 安全领域中应用广泛。

2 身份认证方式

2.1 Session-Cookie 认证

HTTP 是无状态协议,即协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,必须要重传,这样可能导致每次连接传送的数据量增大。HTTP无状态的特性严重阻碍了 Web 应用程序的实现和应用。Cookie 和 Session 就是为了支持客户端与服务器之间的有状态交互而设计的技术。Session-Cookie 方式是开发 Web 应用时最常用的认证方式。它的认证过程如下:
在这里插入图片描述
(1)用户浏览器向服务器发起认证请求,将用户名和密码发送给服务器;
(2)服务器认证用户名和密码,若通过则创建一个 Session 对象,并将用户信息保存到 Session 中。Session 的信息可以是保存到服务器文件、共享外部存储、数据库等存储中,等下次请求时查询验证使用;
(3)同时,服务器会将该 Session 的唯一标识 sessionID,返回给用户浏览器,并保存在 cookie 中;
(4)用户请求其他页面时,浏览器会自动将用户的 cookie 携带上,并发起接口请求,服务端收到请求后,从 cookie 解析出 sessionID, 根据这个 sessionID 查询登录后并保存好的 session,若有则说明用户已登录,然后放行。该方式是 Web 开发中最常用的认证方案,几乎所有的 Web 框架都默认集成了Session-Cookie 的认证方式。

2.2.1 Session 和 Cookie 区别
  • 数据存放位置不同:Session 数据是存在服务器中的,cookie 数据存放在浏览器当中;
  • 安全程度不同:cookie 放在浏览器中不是很安全,session 放在服务器中,相对安全;
  • 性能使用程度不同:session 放在服务器上,访问增多会占用服务器的性能;
  • 数据存储大小不同:单个 cookie 保存的数据不能超过 4K,session 存储在服务端,根据服务器大小来定。
2.2.2 Session-Cookie 认证方式的缺点

session 认证的方式应用非常普遍,但也存在一些问题,扩展性不好,如果是服务器集群,或者是跨域的服务导向架构,就要求 session数据共享,每台服务器都能够读取 session,针对此种问题一般有两种方案:

  • 一种解决方案是session数据持久化,写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。
  • 一种方案是服务器不再保存session数据,所有数据都保存在客户端,每次请求都发回服务器。Token认证就是这种方案的一个代表。
2.3Token 认证方式

Token 即令牌,及确认身份的意思,服务端可以通过令牌来确认身份。和 Session相比,Token 是开发定义的,一般不存储在服务端,而且跨域处理较为方便,多台服务器之间可以共用一个 token,因此是不同系统交互、前后端分离架构中常用的认证方式。

Token 方式的认证流程如下。
在这里插入图片描述
(1)用户使用用户名和密码登录,将用户名和密码发送给服务器;
(2)服务器验证用户名和密码,若正确,则签发 token,返回给用户;
(3)用户收到 token 后,将其存储起来,Web 服务一般为 localStrage 或 cookie;
(4)用户请求其他资源页面时,会携带 token,一般放到 header 或参数中,发送给服
务端;
(5)服务器收到后,验证 token,判断用户的正确性。

在这里插入图片描述
当前,JWT(JSON Web Token)是最常用的一种 Token 认证方式,已成为 Token 认证的标准事实。

2.4 OAuth 方式

OAuth(Open Authorization)是一个开放标准,允许用户授权第三方网站访问他们存储在服务端的用户信息。常见的新浪、京东等使用的第三方登录机制就是 Auth 认证方式。OAuth 更像是一种授权机制。数据的所有者告诉系统,同意授权第三方应用进入系统,获取这些数据。系统从而产生一个短期的进入令牌(token),用来代替密码,供第三方应用使用。在单纯的前后端分离系统中,OAuth 并不是常用的方式,它更多的应用在不同系统
之间的授权交互。

3 JWT 认证方式

JWT(JSON Web Token)方式将 Token 分段,使其可以保持少量数据,还增加了签名验证,确保了 token 的安全性。

3.1 JWT 的认证流程:
  • 服务器生成一个 JSON 对象,发回给用户,如:

{
“姓名”:“H1”,
“角色”:“管理员”,
“到期时间”:”2023 年 1 月 1 日 0 点 0 分”
}

  • 浏览器与服务器通信时,都要发回这个 JSON 对象,服务器依据这个对象来认定
    用户身份;
  • 为了防止用户篡改数据,服务器在生成这个对象时,会加上签名。
3.2 JWT 的构成

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、实验内容

说明几点:
1、项目源码:https://github.com/newbee-ltd/newbee-mall-api
2、本实验NewBeeAdminManageUserAPI.java 与NewBeeAdminOrderAPI.java中相关内容的实验
在这里插入图片描述
3、本实验中将端口改到了8080
在这里插入图片描述

1.搭建新蜂商城的newbee_mall_db_v2数据库,并给出mysql中数据库的截图;

在这里插入图片描述

2.运行newbee-mall-api-master项目,生成Swagger的API接口文档,并给出接口文档的截图;

在这里插入图片描述

3.写出admin包下,自己负责分析的API的类名、主要功能、参数或注解的作用,如果代码较复杂需画出流程图;

NewBeeAdminManageUserAPI.java

相关接口功能描述:

(1)@RequestMapping(value = “/adminUser/login”, method = RequestMethod.POST)

public Result login(@RequestBody @Valid AdminLoginParam adminLoginParam)
主要功能:用于管理员用户登录认证,前端传入管理员的用户名(username)和MD5加密后的密码(passwordMd5),后端Controller层调用Service层相关接口进行认证校验,若登录成功则返回token等登录成功响应体信息。若失败则返回登录失败响应体信息
参数或注解的作用:
@RequestMapping(value = “/adminUser/login”, method = RequestMethod.POST):设置外部映射地址,请求方法为POST
@Valid:对传入的数据进行校验
@RequestBody:用来接收前端传递给后端的json字符串中的数据
AdminLoginParam:这里使用AdminLoginParam作为参数是因为项目中对传入参数进行了封装,在ltd.newbee.mall.api.admin.param包中可以看到对各个接口传入参数进行的封装:

在这里插入图片描述

(2)@RequestMapping(value = “/adminUser/profile”, method = RequestMethod.GET)public Result profile(@TokenToAdminUser AdminUserToken adminUser)

主要功能:用于获取当前登录的管理员用户的相关信息,前端请求头传入token信息,后端对传入的Token进行处理并包装成AdminUserToken类对象后传入该接口处理方法中。后端根据传入的adminUser得到对应管理员用户的id,从而调用adminUserService中的getUserDetailById方法从而得到该用户的信息。若成功取到对应用户的信息则对密码隐去后返回该用户信息与成功返回的响应体信息,若取不到对应用户信息则返回失败响应体对应信息。
参数或注解的作用:
@RequestMapping(value = “/adminUser/profile”, method = RequestMethod.GET):设置外部映射地址,请求方法为GET
@TokenToAdminUser:该注解为自定义的注解,作用是对传入的Token值进行处理后得到该token对应的管理员用户信息并包装成AdminUserToken类型的对象再传入到控制器中的方法中。
在这里插入图片描述

AdminUserToken:传入到方法中的参数数据为包装好后的AdminUserToken类型对象
在这里插入图片描述

(3)@RequestMapping(value = “/adminUser/password”, method = RequestMethod.PUT)public Result passwordUpdate(@RequestBody @Valid UpdateAdminPasswordParam adminPasswordParam, @TokenToAdminUser AdminUserToken adminUser)

主要功能:用于对当前登录的管理员用户的密码进行修改,前端请求头传入token信息还有该用户原来的密码(originalPassword)和新密码(newPassword),后端对传入的Token进行处理并包装成AdminUserToken类对象后传入该接口处理方法中。后端根据传入的adminPasswordParam和adminUser得到对应管理员用户的id、当前密码与新密码,从而调用adminUserService中的updatePassword方法修改该用户的密码。若成功修改该用户密码,则返回含成功信息的响应体信息,若修改该用户失败则返回失败响应体对应信息。

参数或注解的作用:
@RequestMapping(value = “/adminUser/password”, method = RequestMethod.PUT):设置外部映射地址,请求方法为PUT
@Valid:对传入的数据进行校验
@RequestBody:用来接收前端传递给后端的json字符串中的数据
UpdateAdminPasswordParam:这里使用UpdateAdminPasswordParam类型作为参数是因为项目中对传入参数进行了封装,在ltd.newbee.mall.api.admin.param包中可以看到对各个接口传入参数进行的封装:
在这里插入图片描述
@TokenToAdminUser:该注解为自定义的注解,作用是对传入的Token值进行处理后得到该token对应的管理员用户信息并包装成AdminUserToken类型的对象再传入到控制器中的方法中。
在这里插入图片描述
AdminUserToken:传入到方法中的参数数据为包装好后的AdminUserToken类型对象
在这里插入图片描述

(4)@RequestMapping(value = “/adminUser/name”, method = RequestMethod.PUT) public Result nameUpdate(@RequestBody @Valid UpdateAdminNameParam adminNameParam, @TokenToAdminUser AdminUserToken adminUser)

主要功能:用于对当前登录的管理员用户的用户名和昵称进行修改,前端请求头传入token信息还有欲修改的用户名和昵称,后端对传入的Token进行处理并包装成AdminUserToken类对象后传入该接口处理方法中。后端根据传入的adminNameParam和adminUser得到对应管理员用户的id、欲修改的用户名和昵称,从而调用adminUserService中的updateName方法修改该用户的用户名和昵称。若成功修改该用户用户名和昵称,则返回含成功信息的响应体信息,若修改该用户失败则返回失败响应体对应信息。

参数或注解的作用:
@RequestMapping(value = “/adminUser/name”, method = RequestMethod.PUT)
:设置外部映射地址,请求方法为PUT
@Valid:对传入的数据进行校验
@RequestBody:用来接收前端传递给后端的json字符串中的数据
UpdateAdminNameParam:这里使用UpdateAdminNameParam类型作为参数是因为项目中对传入参数进行了封装,在ltd.newbee.mall.api.admin.param包中可以看到对各个接口传入参数进行的封装:
在这里插入图片描述
@TokenToAdminUser:该注解为自定义的注解,作用是对传入的Token值进行处理后得到该token对应的管理员用户信息并包装成AdminUserToken类型的对象再传入到控制器中的方法中。
在这里插入图片描述
AdminUserToken:传入到方法中的参数数据为包装好后的AdminUserToken类型对象

在这里插入图片描述

(5)@RequestMapping(value = “/logout”, method = RequestMethod.DELETE)public Result logout(@TokenToAdminUser AdminUserToken adminUser)

主要功能:用于对当前登录的管理员用户进行登出操作,前端请求头传入token信息,后端对传入的Token进行处理并包装成AdminUserToken类对象后传入该接口处理方法中。后端根据传入的adminUser得到对应管理员用户的id,从而调用adminUserService中的logout方法对该用户进行登出操作。若成功则返回含成功信息的响应体信息,若失败则返回失败响应体对应信息。

参数或注解的作用:
@RequestMapping(value = “/logout”, method = RequestMethod.DELETE):设置外部映射地址,请求方法为DELETE
@TokenToAdminUser:该注解为自定义的注解,作用是对传入的Token值进行处理后得到该token对应的管理员用户信息并包装成AdminUserToken类型的对象再传入到控制器中的方法中。

在这里插入图片描述
AdminUserToken:传入到方法中的参数数据为包装好后的AdminUserToken类型对象
在这里插入图片描述

NewBeeAdminOrderAPI.java

相关接口功能描述:

(1)@RequestMapping(value = “/orders”, method = RequestMethod.GET)@ApiOperation(value = “订单列表”, notes = “可根据订单号和订单状态筛选”) public Result list(@RequestParam(required = false) @ApiParam(value = “页码”) Integer pageNumber, @RequestParam(required = false) @ApiParam(value = “每页条数”) Integer pageSize, @RequestParam(required = false) @ApiParam(value = “订单号”) String orderNo, @RequestParam(required = false) @ApiParam(value = “订单状态”) Integer orderStatus, @TokenToAdminUser AdminUserToken adminUser):

主要功能:根据传入的参数返回订单的列表。前端传入页码和每页条数后,后端对传入的页码和每页条数进行校验,若不符合要求则返回"分页参数异常!"对应响应体信息,前端还可以传入订单号和订单状态参数对结果进行筛选,若页码和每页条数校验成功则将传入的参数封装好后传入newBeeMallOrderService中的getNewBeeMallOrdersPage方法,并返回含有对应订单信息与成功信息的响应体。

参数或注解的作用:
@RequestMapping(value = “/orders”, method = RequestMethod.GET):设置外部映射地址,请求方法为GET
@ApiOperation(value = “订单列表”, notes = “可根据订单号和订单状态筛选”):配置Swagger文档中对该接口的说明
@ApiParam(value = “”):配置Swagger文档中对该参数的说明
@RequestParam(required = false):@RequestParam(value = “key”,required = false)value = “key”,表示前端对传入参数指定为key,如果前端不传key参数名,会报错。required = false表示该参数可以不传,required在一个请求中默认值是为true。
如@RequestParam(value=“username”,required=true,defaultValue=“admin”) defaultValue默认值,如果传输参数没有匹配上则使用默认值,若匹配上则使用传输过来的内容
注意:如果@requestParam注解的参数是int类型,并且required=false,此时如果不传参数的话,会报错。原因是,required=false时,不传参数的话,会给参数赋值null,这样就会把null赋值给了int,因此会报错。
pageNumber:页码
pageSize:每页条数
orderNo:订单号
orderStatus:订单状态
@TokenToAdminUser:该注解为自定义的注解,作用是对传入的Token值进行处理后得到该token对应的管理员用户信息并包装成AdminUserToken类型的对象再传入到控制器中的方法中。
在这里插入图片描述
AdminUserToken:传入到方法中的参数数据为包装好后的AdminUserToken类型对象
在这里插入图片描述

(2)@GetMapping(“/orders/{orderId}”)@ApiOperation(value = “订单详情接口”, notes = “传参为订单号”)public Result orderDetailPage(@ApiParam(value = “订单号”) @PathVariable(“orderId”) Long orderId, @TokenToAdminUser AdminUserToken adminUser):

主要功能:根据传入的订单号返回对应订单的详情。前端传入订单号后将订单id传入newBeeMallOrderService中的getOrderDetailByOrderId方法,并返回含有对应订单详情与成功信息的响应体。

参数或注解的作用:
@GetMapping(“/orders/{orderId}”):设置外部映射地址,请求方法为GET
@ApiOperation(value = “订单详情接口”, notes = “传参为订单号”)
:配置Swagger文档中对该接口的说明
@ApiParam(value = “”):配置Swagger文档中对该参数的说明
@PathVariable(“orderId”):接收请求路径中占位符{orderId}的值
orderId:订单id
@TokenToAdminUser:该注解为自定义的注解,作用是对传入的Token值进行处理后得到该token对应的管理员用户信息并包装成AdminUserToken类型的对象再传入到控制器中的方法中。
在这里插入图片描述
AdminUserToken:传入到方法中的参数数据为包装好后的AdminUserToken类型对象
在这里插入图片描述

(3)@RequestMapping(value = “/orders/checkDone”, method = RequestMethod.PUT)@ApiOperation(value = “修改订单状态为配货成功”, notes = “批量修改”)public Result checkDone(@RequestBody BatchIdParam batchIdParam, @TokenToAdminUser AdminUserToken adminUser) :

主要功能:根据传入的订单id数组批量修改订单状态为配货成功。前端传入订单id数组后,后端对传入的数组进行校验,若校验失败则返回含"参数异常!"信息的失败信息响应体,若数组校验成功,则将订单id数组传入newBeeMallOrderService中的checkDone方法,并对方法返回结果进行校验,若修改成功则返回成功信息的响应体,否则返回失败信息的响应体。

参数或注解的作用:
@RequestMapping(value = “/orders/checkDone”, method = RequestMethod.PUT):设置外部映射地址,请求方法为PUT
@ApiOperation(value = “修改订单状态为配货成功”, notes = “批量修改”)
:配置Swagger文档中对该接口的说明
@RequestBody:用来接收前端传递给后端的json字符串中的数据
BatchIdParam:这里使用BatchIdParam类型作为参数是因为项目中对传入参数进行了封装,在ltd.newbee.mall.api.admin.param包中可以看到对各个接口传入参数进行的封装:
在这里插入图片描述
@TokenToAdminUser:该注解为自定义的注解,作用是对传入的Token值进行处理后得到该token对应的管理员用户信息并包装成AdminUserToken类型的对象再传入到控制器中的方法中。
在这里插入图片描述
AdminUserToken:传入到方法中的参数数据为包装好后的AdminUserToken类型对象
在这里插入图片描述

(4)@RequestMapping(value = “/orders/checkOut”, method = RequestMethod.PUT)@ApiOperation(value = “修改订单状态为已出库”, notes = “批量修改”)public Result checkOut(@RequestBody BatchIdParam batchIdParam, @TokenToAdminUser AdminUserToken adminUser):

主要功能:根据传入的订单id数组批量修改订单状态为已出库。前端传入订单id数组后,后端对传入的数组进行校验,若校验失败则返回含"参数异常!"信息的失败信息响应体,若数组校验成功,则将订单id数组传入newBeeMallOrderService中的checkOut方法,并对方法返回结果进行校验,若修改成功则返回成功信息的响应体,否则返回失败信息的响应体。

参数或注解的作用:
@RequestMapping(value = “/orders/checkOut”, method = RequestMethod.PUT):设置外部映射地址,请求方法为PUT
@ApiOperation(value = “修改订单状态为已出库”, notes = “批量修改”):配置Swagger文档中对该接口的说明
@RequestBody:用来接收前端传递给后端的json字符串中的数据
BatchIdParam:这里使用BatchIdParam类型作为参数是因为项目中对传入参数进行了封装,在ltd.newbee.mall.api.admin.param包中可以看到对各个接口传入参数进行的封装:
在这里插入图片描述
@TokenToAdminUser:该注解为自定义的注解,作用是对传入的Token值进行处理后得到该token对应的管理员用户信息并包装成AdminUserToken类型的对象再传入到控制器中的方法中。
在这里插入图片描述
AdminUserToken:传入到方法中的参数数据为包装好后的AdminUserToken类型对象
在这里插入图片描述

(5)@RequestMapping(value = “/orders/close”, method = RequestMethod.PUT) @ApiOperation(value = “修改订单状态为商家关闭”, notes = “批量修改”)public Result closeOrder(@RequestBody BatchIdParam batchIdParam, @TokenToAdminUser AdminUserToken adminUser) :

主要功能:根据传入的订单id数组批量修改订单状态为商家关闭。前端传入订单id数组后,后端对传入的数组进行校验,若校验失败则返回含"参数异常!"信息的失败信息响应体,若数组校验成功,则将订单id数组传入newBeeMallOrderService中的closeOrder方法,并对方法返回结果进行校验,若修改成功则返回成功信息的响应体,否则返回失败信息的响应体。

参数或注解的作用:
@RequestMapping(value = “/orders/close”, method = RequestMethod.PUT):设置外部映射地址,请求方法为PUT
@ApiOperation(value = “修改订单状态为商家关闭”, notes = “批量修改”):配置Swagger文档中对该接口的说明
@RequestBody:用来接收前端传递给后端的json字符串中的数据
BatchIdParam:这里使用BatchIdParam类型作为参数是因为项目中对传入参数进行了封装,在ltd.newbee.mall.api.admin.param包中可以看到对各个接口传入参数进行的封装:
在这里插入图片描述
@TokenToAdminUser:该注解为自定义的注解,作用是对传入的Token值进行处理后得到该token对应的管理员用户信息并包装成AdminUserToken类型的对象再传入到控制器中的方法中。
在这里插入图片描述
AdminUserToken:传入到方法中的参数数据为包装好后的AdminUserToken类型对象
在这里插入图片描述

4.写出上述API调用的Service层的接口名和实现类名、主要功能、参数或注解的作用,如果代码较复杂需画出流程图;

接口名:AdminUserService.java

对应实现类名:AdminUserServiceImpl.java
相关方法描述:

(1)String login(String userName, String password);

主要功能:该方法接收到Controller层传入的userName和password参数后传入adminUserMapper中的login方法中,并对返回的对象进行校验,若返回的对象不为空则对该用户签发token,然后根据该用户id查看该用户是否已有token,若该用户未登录过(无对应token信息)则将对应token信息存入数据库中,若用户已登录过(有对应token信息)则用对应token信息更新数据库中原有的对应token信息。若成功完成登录操作则返回token,否则返回错误信息。
参数或注解的作用:
userName:Controller层传入的登录信息中的用户名
password:Controller层传入的登录信息中的密码

(2)AdminUser getUserDetailById(Long loginUserId);

主要功能:该方法接收到Controller层传入的loginUserId后传入adminUserMapper中的selectByPrimaryKe方法中,并将获得的该用户id对应的详情信息返回。
参数或注解的作用:
loginUserId:Controller层传入的管理员用户id

(3)Boolean updatePassword(Long loginUserId, String originalPassword, String newPassword);

主要功能:该方法接收到Controller层传入的loginUserId、originalPassword和newPassword参数后,将loginUserId传入adminUserMapper中的selectByPrimaryKey方法中,得到该用户id对应的用户对象,若该对象不为空,则比较传入的originalPassword与该用户对象的密码是否一致,若一致,则将该用户密码更新为newPassword,且删除该用户对应的登录token信息,若操作成功则返回true,否则返回false。
参数或注解的作用:
loginUserId:Controller层传入的管理员用户id
originalPassword:Controller层传入的原密码
newPassword:Controller层传入的新密码

(4)Boolean updateName(Long loginUserId, String loginUserName, String nickName);

主要功能:该方法接收到Controller层传入的loginUserId、loginUserName和nickName参数后,将loginUserId传入adminUserMapper中的selectByPrimaryKey方法中,得到该用户id对应的用户对象,若该对象不为空,则为该用户设置新的用户名loginUserName与昵称nickName,若修改成功则返回true,否则返回false。
参数或注解的作用:
loginUserId:Controller层传入的管理员用户id
loginUserName:Controller层传入的新用户名
nickName:Controller层传入的新昵称

(5)Boolean logout(Long adminUserId);

**主要功能:**该方法接收到Controller层传入的loginUserId后传入newBeeAdminUserTokenMapper中的deleteByPrimaryKey方法中,对对应管理员用户进行登出操作(删除对应的token)。
参数或注解的作用:
loginUserId:Controller层传入的管理员用户id

接口名:NewBeeMallOrderService.java

实现类名:NewBeeMallOrderServiceImpl.java
相关方法描述:

(1)NewBeeMallOrderDetailVO getOrderDetailByOrderId(Long orderId);

主要功能:该方法接收到Controller层传入的orderId后传入newBeeMallOrderMapper中的selectByPrimaryKey方法中,获得对应订单相关信息对象,若该对象为空则抛出异常信息,若不为空,则将订单id传入到newBeeMallOrderItemMapper中的selectByOrderId方法获得对应订单详情信息对象。若对象不为空,则将获得的List集合类型对象包装成NewBeeMallOrderDetailVO类型对象后返回。若对象为空则抛出异常且返回空。
在这里插入图片描述
参数或注解的作用:
orderId:Controller层传入的订单id

(2)PageResult getNewBeeMallOrdersPage(PageQueryUtil pageUtil);

主要功能:该方法接收到Controller层传入的PageQueryUtil类型参数,该类型参数中含有页码与每页条数信息,将pageUtil传入newBeeMallOrderMapper中的findNewBeeMallOrderList方法中,得到对应页码和条数的订单信息,并将相相关信息封装成PageResult类型的对象返回。
在这里插入图片描述
参数或注解的作用:
pageUtil:Controller层传入的分页信息,包含当前页码和每页条数信息

(3)String checkDone(Long[] ids);

主要功能:该方法接收到Controller层传入的ids数组,处理后传入newBeeMallOrderMapper中的selectByPrimaryKeys方法,获得对应订单信息的List集合,若集合不为空,则判断每个订单是否已被删除、是否已支付成功,若已被删除或者未支付成功,则将对应订单id拼接到errorOrderNos中,如果所有订单状态正常,则可以执行配货完成操作 修改订单状态和更新时间,成功则返回成功相关信息,否则返回失败相关信息,若有订单状态不正常,则订单此时不执行配货完成操作,并返回对应提示信息。若集合为空,则返回对应错误信息。
参数或注解的作用:
@Transactional:开启事务处理,代码执行出错的时候能够进行事务的回滚。

(4)String checkOut(Long[] ids);

主要功能:该方法接收到Controller层传入的ids数组,处理后传入newBeeMallOrderMapper中的selectByPrimaryKeys方法,获得对应订单信息的List集合,若集合不为空,则判断每个订单是否已被删除、支付成功或配货完成,若已被删除或者未支付成功或者未配货成功,则将对应订单id拼接到errorOrderNos中,如果所有订单状态正常,则可以执行出库操作 修改订单状态和更新时间,成功则返回成功相关信息,否则返回失败相关信息,若有订单状态不正常,则订单此时不执行出库操作,并返回对应提示信息。若集合为空,则返回对应错误信息。
参数或注解的作用
@Transactional:开启事务处理,代码执行出错的时候能够进行事务的回滚。

(5)String closeOrder(Long[] ids);

主要功能:该方法接收到Controller层传入的ids数组,处理后传入newBeeMallOrderMapper中的selectByPrimaryKeys方法,获得对应订单信息的List集合,若集合不为空,则判断每个订单是否已被删除、是否已关闭或者已完成无法关闭订单,若已被删除或者已关闭或者已完成无法关闭,则将对应订单id拼接到errorOrderNos中,如果所有订单状态正常,可以执行关闭操作 修改订单状态和更新时间,成功则返回成功相关信息,否则返回失败相关信息,若有订单状态不正常,则订单此时不执行关闭操作,并返回对应提示信息。若集合为空,则返回对应错误信息。
参数或注解的作用:
@Transactional:开启事务处理,代码执行出错的时候能够进行事务的回滚。

5.写出上述Service实现类调用的mapper层的接口名和对应mapper.sql文件、主要功能、参数或注解的作用,对较复杂的方法需画出流程图;

接口文件名:AdminUserMapper.java

对应映射文件名:AdminUserMapper.xml
相关接口方法描述:

(1)AdminUser login(@Param(“userName”) String userName, @Param(“password”) String password);

主要功能:根据service层传入的管理员用户名userName与密码password查询数据库中对应的管理员用户并封装成AdminUser类型对象返回。
参数或注解的作用:
@Param:给参数命名
userName:Service层传入的管理员用户名
password:Service层传入的管理员密码

(2)AdminUser selectByPrimaryKey(Long adminUserId);

主要功能:根据service层传入的管理员用户id(adminUserId)查询数据库中对应的管理员用户并封装成AdminUser类型对象返回。
参数或注解的作用:
adminUserId:Service层传入的管理员用户id

(3)int updateByPrimaryKeySelective(AdminUser record);

主要功能:根据service层传入的管理员用户对象,根据该对象中的id修改对应用户的用户名和昵称或密码。
参数或注解的作用:
record:Service层传入的管理员用户信息

接口文件名:NewBeeAdminUserTokenMapper

对应映射文件名:NewBeeAdminUserTokenMapper.xml
相关接口方法描述:

(1)int deleteByPrimaryKey(Long userId);

主要功能:根据service层传入的管理员用户id(userId)删除tb_newbee_mall_admin_user_token表中对应用户的token信息
参数或注解的作用:
userId:Service层传入的管理员用户id

(2)int insertSelective(AdminUserToken record);

主要功能:根据service层传入的管理员用户对象,向tb_newbee_mall_admin_user_token表中插入该管理员用户对应的登录token、更新时间、失效时间等信息
参数或注解的作用:
record:Service层传入的管理员用户登录相关信息

(3)AdminUserToken selectByPrimaryKey(Long userId);

主要功能:根据service层传入的管理员用户id(userId)查询tb_newbee_mall_admin_user_token表中对应用户的登录token、更新时间、失效时间等信息,并封装成AdminUserToken类型对象返回。
参数或注解的作用
userId:Service层传入的管理员用户id

(4)int updateByPrimaryKeySelective(AdminUserToken record);

主要功能:根据service层传入的管理员用户对象,向tb_newbee_mall_admin_user_token表中更新该管理员用户对应的登录token、更新时间、失效时间等信息
参数或注解的作用:
record:Service层传入的管理员用户登录相关信息

接口文件名:newBeeMallOrderMapper.java

对应映射文件名:NewBeeMallOrderMapper.xml
相关接口方法描述:

(1)NewBeeMallOrder selectByPrimaryKey(Long orderId);

主要功能:根据service层传入的订单id(orderId)查询tb_newbee_mall_order表中对应订单的信息,并封装成NewBeeMallOrder类型对象返回。
参数或注解的作用:
orderId:Service层传入的订单id

(2)List findNewBeeMallOrderList(PageQueryUtil pageUtil);

主要功能:根据service层传入的PageQueryUtil 类型参数,查询tb_newbee_mall_order表中对应订单的信息,并根据传入的pageUtil中的页码与每页条数对返回结果进行处理并返回。
参数或注解的作用:
pageUtil:Service层传入的PageQueryUtil 类型参数,包含当前页码,每页条数等信息。
在这里插入图片描述

(3)int getTotalNewBeeMallOrders(PageQueryUtil pageUtil);

主要功能:根据service层传入的PageQueryUtil 类型参数,查询tb_newbee_mall_order表中订单数量
参数或注解的作用:
pageUtil:Service层传入的PageQueryUtil 类型参数,包含当前页码,每页条数等信息。

(4)int checkOut(@Param(“orderIds”) List orderIds);

主要功能:根据service层传入的订单id集合orderIds,更新tb_newbee_mall_order表中订单id对应订单的订单状态(order_status)为已出库(3),并更新对应订单的更新时间(update_time)
参数或注解的作用:
orderIds:Service层传入的订单id集合
@Param:给参数命名

(5)int closeOrder(@Param(“orderIds”) List orderIds, @Param(“orderStatus”) int orderStatus);

主要功能:根据service层传入的订单id集合orderIds与orderStatus,更新tb_newbee_mall_order表中订单id对应订单的订单状态(order_status)为orderStatus(-3:代表商家关闭),并更新对应订单的更新时间(update_time)
参数或注解的作用:
orderIds:Service层传入的订单id集合
orderStatus:Service层传入的订单状态代码(-3:代表商家关闭)

在这里插入图片描述
在这里插入图片描述
@Param:给参数命名

(6)int checkDone(@Param(“orderIds”) List asList);

主要功能:根据service层传入的订单id集合asList,更新tb_newbee_mall_order表中订单id对应订单的订单状态(order_status)为配货完成(2),并更新对应订单的更新时间(update_time)
参数或注解的作用:
asList:Service层传入的订单id集合
@Param:给参数命名

接口文件名:newBeeMallOrderItemMapper.java

对应映射文件名:NewBeeMallOrderItemMapper.xml
相关接口方法描述:

(1)List selectByOrderId(Long orderId);

主要功能:根据service层传入的订单id(orderId),查询tb_newbee_mall_order_item表中该订单id对应订单的详情信息
参数或注解的作用:
orderId:Service层传入的订单id

6.补充说明:以上接口或类实现代码中用到的一些工具类、配置类等;

相关配置类:

自定义注解:TokenToAdminUser.java
注解处理类:TokenToAdminUserMethodArgumentResolver.java
主要功能:在接口方法传入参数中加上该注解可以对前端传入的管理员用户token值进行处理并得到对应的管理员用户对象(AdminUserToken类型对象)

在这里插入图片描述

相关工具类:

PageQueryUtil.java
主要功能:分页处理工具类,对于前端传入的页码与每页条数进行分页处理。
PageResult.java
主要功能:封装分页查询返回信息
在这里插入图片描述
Result.java
主要功能:封装返回响应体信息
在这里插入图片描述
SystemUtil.java
主要功能:其中的genToken方法用于生成token值

7.对API接口中的各个方法进行接口测试,并给出测试截图;

NewBeeAdminManageUserAPI.java

接口url:localhost:8080/manage-api/v1/adminUser/login 请求方法:POST
对应方法:public Result login(@RequestBody @Valid AdminLoginParam adminLoginParam)
测试结果:
在这里插入图片描述
可以看到测试成功,返回信息中”data”对应的即为token值

接口url:localhost:8080/manage-api/v1/adminUser/profile 请求方法:GET
对应方法:public Result profile(@TokenToAdminUser AdminUserToken adminUser)
头部Header设置:需要设置token
在这里插入图片描述
测试结果:
在这里插入图片描述
可以看到成功返回当前管理员用户信息

接口url:localhost:8080/manage-api/v1/adminUser/password 请求方法:PUT
对应方法:public Result passwordUpdate(@RequestBody @Valid UpdateAdminPasswordParam adminPasswordParam, @TokenToAdminUser AdminUserToken adminUser)
头部Header设置:需要设置token
在这里插入图片描述
测试结果:
在这里插入图片描述
查看数据库:
在这里插入图片描述
可以看到当前管理员用户登录密码修改成功

接口url:localhost:8080/manage-api/v1/adminUser/name 请求方法:PUT
对应方法:public Result nameUpdate(@RequestBody @Valid UpdateAdminNameParam adminNameParam, @TokenToAdminUser AdminUserToken adminUser)
头部Header设置:需要设置token
在这里插入图片描述
测试结果:
在这里插入图片描述
查看数据库:

在这里插入图片描述
可以看到当前管理员用户的昵称修改成功

接口url:localhost:8080/manage-api/v1/logout 请求方法:DELETE
对应方法:public Result logout(@TokenToAdminUser AdminUserToken adminUser)
头部Header设置:需要设置token
在这里插入图片描述
测试结果:
在这里插入图片描述
查看数据库中tb_newbee_mall_admin_user_token表:
在这里插入图片描述
可见当前管理员用户的token已被成功删除

NewBeeAdminOrderAPI.java:

接口url:http://localhost:8080/manage-api/v1/orders?pageNumber=1&pageSize=10
请求方法:GET
功能描述:根据传入的参数获取订单列表
对应方法:public Result list(@RequestParam(required = false) @ApiParam(value = “页码”) Integer pageNumber,
@RequestParam(required = false) @ApiParam(value = “每页条数”) Integer pageSize,
@RequestParam(required = false) @ApiParam(value = “订单号”) String orderNo,
@RequestParam(required = false) @ApiParam(value = “订单状态”) Integer orderStatus, @TokenToAdminUser AdminUserToken adminUser)

注意:这个接口测试之前需要先创建订单,因此还需要额外运行和测试几个接口:
1、接口url:localhost:8080/api/v1/user/login 请求方法:POST
接口功能:商城用户登录
测试结果:
在这里插入图片描述

2、接口url:localhost:8080/api/v1/shop-cart 请求方法:POST
接口功能:添加商品到购物车
需要在头部Header中设置token:
在这里插入图片描述
测试结果:
在这里插入图片描述
查看数据库:

在这里插入图片描述
可以看到对应商品成功加入购物车

3、接口url:localhost:8080/api/v1/address 请求方法:POST
接口功能:添加地址
需要在头部Header中设置token:
在这里插入图片描述
测试结果;
在这里插入图片描述
查看数据库:
在这里插入图片描述
可以看到地址成功添加到数据库中

4、接口url:localhost:8080/api/v1/saveOrder 请求方法:POST
接口功能:生成订单
需要在头部Header中设置token:
在这里插入图片描述
测试结果:
在这里插入图片描述

查看数据库:
在这里插入图片描述
可以看到订单成功保存到数据库中

运行以上几项接口后,此时对该接口进行测试:
接口url:http://localhost:8080/manage-api/v1/orders?pageNumber=1&pageSize=10
请求方法:GET
功能描述:根据传入的参数获取订单列表
需要在头部Header中设置token:
在这里插入图片描述
测试结果:
在这里插入图片描述
可见成功返回订单列表

接口url:http://localhost:8080/manage-api/v1/orders/{orderId}
测试接口url:http://localhost:8080/manage-api/v1/orders/1
请求方法:GET
功能描述:根据传入的订单号获取订单详情
需要在头部Header中设置token:
在这里插入图片描述
在这里插入图片描述
可见成功返回对应订单的详情信息

接口url:http://localhost:8080/manage-api/v1/orders/checkDone
请求方法:PUT
功能描述:修改订单状态为配货成功

注意进行该接口测试时需先运行订单支付功能对应的接口才能进行正常的测试
接口url:http://localhost:8080/api/v1/paySuccess?orderNo=16698864444591115&payType=2
请求方法:GET
功能描述:模拟支付成功回调的接口
运行结果:
在这里插入图片描述
查看数据库表:

在这里插入图片描述
可以看到对应订单信息已变更

此时再对该接口进行测试:
接口url:http://localhost:8080/manage-api/v1/orders/checkDone
请求方法:PUT
功能描述:修改订单状态为配货成功
需要在头部Header中设置token:
在这里插入图片描述
在这里插入图片描述
接口url:http://localhost:8080/manage-api/v1/orders/checkOut
请求方法:PUT
功能描述:修改订单状态为已出库
需要在头部Header中设置token:
在这里插入图片描述

测试结果:
在这里插入图片描述

接口url:http://localhost:8080/manage-api/v1/orders/close
请求方法:PUT
功能描述:修改订单状态为商家关闭
需要在头部Header中设置token:

在这里插入图片描述
测试结果;
在这里插入图片描述

  • 13
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GCTTTTTT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值