乐优商城(二)搭建后台前端

news2024/11/15 13:44:38

1. 搭建后台管理前端

1.1 导入已有资源

  1. 找到已经准备好的 leyou-manage-web 压缩文件,这就是后台管理的前端项目

  2. 解压 leyou-manage-web 文件到项目中,注意与 leyou 文件同级

1.2 安装依赖

  1. 在 IDEA 中打开 leyou-manage-web 工程

2.打开 Teminal,输入以下命令安装依赖

npm install

1.3 启动项目

  1. 在 package.json 文件中有 scripts 启动脚本配置

2.打开 Teminal,输入以下命令启动项目

npm start

3.启动成功

4.打开浏览器,访问以下地址

http://localhost:9001

1.4 目录结构

1.5 调用关系

2. Vuetify 框架

2.1 为什么使用 UI 框架

Vue 虽然会帮我们进行视图的渲染,但样式还是由我们自己来完成。这显然不是我们的强项,因此后端开发人员一般都喜欢使用一些现成的 UI 组件,拿来即用,常见的例如:

  • BootStrap
  • LayUI
  • EasyUI
  • ZUI

然而这些 UI 组件的基因天生与 Vue 不合,因为他们更多的是利用 DOM 操作,借助于 jQuery 实现,而不是 MVVM 的思想。而目前与 Vue 吻合的 UI 框架也非常的多,国内比较知名的如:

  • element-ui
  • i-view

我们使用的是一款国外的框架:Vuetify

官方网站:https://vuetifyjs.com/zh-Hans/

2.2 为什么使用 Vuetify

使用 Vuetify 原因如下:

  • Vuetify 几乎不需要任何 CSS 代码,而 element-ui 许多布局样式需要我们来编写
  • Vuetify 从底层构建起来的语义化组件。简单易学,容易记住
  • Vuetify 基于 Material Design(谷歌推出的多平台设计规范),更加美观,动画效果酷炫,且风格统一

3. 使用域名访问本地项目

3.1 统一环境

我们现在访问页面使用的地址是:http://localhost:9001

但实际开发中会有不同的环境:

  • 开发环境:自己的电脑
  • 测试环境:提供给测试人员使用的环境
  • 预发布环境:数据是和生成环境的数据一致,运行最新的项目代码进去测试
  • 生产环境:项目最终发布上线的环境

如果不同环境使用不同的 IP 去访问,可能会出现一些问题。为了保证所有环境的一致,我们会在各种环境下都使用域名来访问。

我们将使用以下域名:

  • 主域名:www.leyou.com,leyou.com
  • 管理系统域名:manage.leyou.com
  • 网关域名:api.leyou.com

3.2 域名解析

当我们在浏览器输入一个域名时,浏览器是如何找到对应服务的 IP 和端口的呢?

  • 本地域名解析

    浏览器会首先在本机的 hosts 文件中查找域名映射的 IP 地址,如果查找到就返回 IP 地址,没找到则进行域名服务器解析。

  • 域名服务器解析

    本地解析失败,才会进行域名服务器解析,域名服务器就是网络中的一台计算机,里面记录了所有注册备案的域名和 IP 映射关系。

3.3 解决域名解析问题

我们还在开发阶段,不可能去买个域名,因此我们可以修改本地的 hosts 文件,实现对域名的解析。

  1. 下载并安装 SwitchHosts(这是一个管理 hosts 的工具)

  2. 右键 SwitchHosts,以管理员身份运行

  3. 添加映射关系,点击左边按钮生效

现在试试能不能 Ping 通

打开 leyou-manage-web 工程,在 webpack.dev.conf.js 中取消 host 验证,添加 disableHostCheck: true

启动 leyou-manage,通过域名访问:http://manage.leyou.com:9001

3.4 Nginx 解决端口问题

域名问题解决了,但是现在要访问后台页面,还得自己加上端口:http://manage.leyou.com:9001

这就不够优雅了,我们希望的是直接域名访问:http://manage.leyou.com

这种情况下端口默认是 80,如何才能把请求转移到 9001 端口呢?这里就要用到反向代理工具:Nginx

3.4.1 Nginx 介绍

Nginx 是一个高性能的 Web 和反向代理服务器, 它具有有很多非常优越的特性:

  • 作为 Web 服务器:相比 Apache,Nginx 使用更少的资源,支持更多的并发连接,体现更高的效率,这点使 Nginx 尤其受到虚拟主机提供商的欢迎。能够支持高达 50,000 个并发连接数的响应,感谢 Nginx 为我们选择了 epoll and kqueue 作为开发模型
  • 作为负载均衡服务器:Nginx 既可以在内部直接支持 Rails 和 PHP,也可以支持作为 HTTP 代理服务器 对外进行服务。Nginx 用 C 编写, 不论是系统资源开销还是 CPU 使用效率都比 Perlbal 要好的多。
  • 作为邮件代理服务器: Nginx 同时也是一个非常优秀的邮件代理服务器(最早开发这个产品的目的之一也是作为邮件代理服务器),Last.fm 描述了成功并且美妙的使用经验。
  • Nginx 安装非常的简单,配置文件非常简洁(还能够支持 perl 语法),Bugs 非常少的服务器:Nginx 启动特别容易,并且几乎可以做到 7*24 不间断运行,即使运行数个月也不需要重新启动。你还能够在 不间断服务的情况下进行软件版本的升级。
3.4.2 Nginx 作为 Web 服务器

Web 服务器分两类:

  • Web 应用服务器:
    • Tomcat
    • Jetty
  • Web 服务器:
    • Apache
    • Nginx

二者区分:

  • Web 服务器不能解析 jsp 等页面,只能处理 html、css、js 等静态资源。
  • Web 服务器的并发能力远高于Web 应用服务器。
3.4.3 Nginx 作为网关

Nginx 可以作为 Web 服务器,但更多的时候,我们把它作为网关,因为它具备网关必备的功能:

  • 反向代理
  • 负载均衡
  • 动态路由
  • 请求过滤
3.4.4 Nginx 作为反向代理服务器

正向代理介绍

张三找李四借钱,李四觉得张三不可靠,不借钱给张三。于是张三找到王五,请王五去找李四借钱。王五找李四借钱,李四觉得王五可靠,把钱借给了王五。但是李四并不知道这个钱是借给了张三,李四是借给了王五,最后是王五把钱借给了张三。在这个借钱的过程中,王五就是代理,也可以说是正向代理,他隐藏了真实的借钱人。

正向代理的过程中,让一台服务器代理客户端,客户端的所有请求都交给代理服务器处理,这样就隐藏了真实的客户端。

反向代理介绍

我们打电话给 10086 客服,可能一个地区会有几十个客服,我们不知道会是其中的哪一个客服接电话,但一定会有一个客服接电话。在这个打电话给 10086 的过程中,10086 这个总机号码就是反向代理,它隐藏了真实的客服。

反向代理的过程中,让一台服务器代理真实服务器,用户访问时,不再是访问真实服务器,而是代理服务器,这样就隐藏了真实的服务器。

Nginx 作为反向代理服务器

Nginx 可以作为反向代理服务器来使用:

  • 我们需要提前在 Nginx 中配置好反向代理的规则,不同的请求,交给不同的真实服务器处理
  • 当请求到达 Nginx,Nginx 会根据已经定义的规则进行请求的转发,从而实现路由功能,这样就可以解决我们前面所说的端口问题了

3.4.5 安装和使用 Nginx
  1. 下载并解压 Nginx,以下是目录结构

  • conf:配置目录
  • contrib:第三方依赖
  • html:默认的静态资源目录
  • logs:日志目录
  • nginx.exe:启动程序

2.打开 conf/nginx.conf,看到 server 配置(Nginx 中的每个 server 就是一个反向代理配置,可以有多个 server)

3.修改配置文件如下

#user  nobody;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
   
    keepalive_timeout  65;

    gzip  on;
	server {
        listen       80;
        server_name  manage.leyou.com;

        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        location / {
			proxy_pass http://127.0.0.1:9001;
			proxy_connect_timeout 600;
			proxy_read_timeout 600;
        }
    }
	server {
        listen       80;
        server_name  api.leyou.com;

        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        location / {
			proxy_pass http://127.0.0.1:10010;
			proxy_connect_timeout 600;
			proxy_read_timeout 600;
        }
    }
}

在 Nginx 文件夹中打开 cmd,启动 Nginx

  1. 启动:start nginx.exe
  2. 停止:nginx.exe -s stop
  3. 重新加载:nginx.exe -s reload

打开浏览器,访问 http://manage.leyou.com

总结实现域名访问的具体流程

4. 实现商品分类查询

商城的核心自然是商品,而商品多了以后,肯定要进行分类,并且不同的商品会有不同的品牌信息,我们需要依次去完成:商品分类、品牌、商品的开发。

4.1 导入数据库

  1. 导入准备好的 sql 文件到 MySQL 数据库

打开商品分类表 tb_category

CREATE TABLE `tb_category` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '类目id',
  `name` varchar(32) NOT NULL COMMENT '类目名称',
  `parent_id` bigint(20) NOT NULL COMMENT '父类目id,顶级类目填0',
  `is_parent` tinyint(1) NOT NULL COMMENT '是否为父节点,0为否,1为是',
  `sort` int(4) NOT NULL COMMENT '排序指数,越小越靠前',
  PRIMARY KEY (`id`),
  KEY `key_parent_id` (`parent_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1424 DEFAULT CHARSET=utf8 COMMENT='商品类目表,类目和商品(spu)是一对多关系,类目与品牌是多对多关系';

因为商品分类会有层级关系,因此这里我们加入了 parent_id 字段,对本表中的其它分类进行自关联。

4.2 实现展示静态数据

  1. 在浏览器点击分类管理菜单,可以看到路由路径 item/category

找到路由文件 index.js,发现页面最终指向 /page/item/Category

打开 Category.vue

商品分类使用了树状结构,这里是自定义了一个树状组件,可以参照文档使用该组件

我们发现只要加入 treeData 就可以展示静态数据了,并且不会再去远程加载数据

在 mockDB.js 中已经存在了 treeData 的静态数据

引入 treeData 到 Category.vue 中

打开浏览器,点击商品管理下的分类管理菜单

实现了展示静态数据,我们就知道了分类管理大致要做成什么样子了,接下来就可以取消引入的静态数据了,下面准备实现展示动态数据

4.3 实现展示动态数据

4.3.1 url 异步请求

点击商品管理下的分类管理菜单,打开浏览器控制台

可以看到页面发起了一条请求:

http://api.leyou.com/api/item/category/list?pid=0

这个请求路径怎么来的呢?

Category.vue 中我们使用了相对路径:

/item/category/list

讲道理发起的请求地址应该是:

http://manage.leyou.com/item/category/list

但实际却发起的请求地址是:

http://api.leyou.com/api/item/category/list?pid=0

这是因为我们有一个全局的配置文件,对所有的请求路径进行了约定:

基本路径是 http://api.leyou.com,并且默认加上了 /api 的前缀,这恰好与我们的网关设置匹配。

接下来,我们要做的事情就是编写后台接口,返回对应的数据即可。

4.3.2 实体类
  1. 在 leyou-item-interface 中添加通用 Mapper 依赖

<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
</dependency>

在 leyou-item-interface 中添加实体类

@Table(name = "tb_category")
public class Category {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private Long parentId;
    private Boolean isParent; // 注意 isParent 生成的 getter 和 setter 方法需要手动加上 Is
    private Integer sort;

    // 构造器、getter 和 setter 方法、toString 方法省略
}

注意:在阿里巴巴约规手册中,规定了"POJO 类中布尔类型的变量,都不要加 is 前缀"

4.3.3 Mapper
  1. 在 leyou-item-service 中添加 Mapper 接口

public interface CategoryMapper extends Mapper<Category> {
}

在启动类上添加扫描 Mapper 注解

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.leyou.item.mapper")
public class LeyouItemServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(LeyouItemServiceApplication.class, args);
    }
}

4.3.4 Service

在 leyou-item-service 中添加 CategoryService

@Service
public class CategoryService {
    @Autowired
    private CategoryMapper categoryMapper;

    /**
     * 根据 ParentId 查询子类目
     * @param pid
     * @return
     */
    public List<Category> queryCategoryById(Long pid) {
        Category category = new Category();
        category.setParentId(pid);
        List<Category> categories = categoryMapper.select(category);
        return categories;
    }
}

4.3.5 Controller

在 leyou-item-service 中添加 CategoryController

@RestController
@RequestMapping("/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;
	
	/**
     * 根据 ParentId 查询子类目
     * @param pid
     * @return
     */
    @GetMapping("/list")
    public ResponseEntity<List<Category>> queryCategoryById(@RequestParam("pid") Long pid) {
        if (pid == null || pid.longValue() < 0) {
            return ResponseEntity.badRequest().build(); // 响应 400
        }
        List<Category> categories = categoryService.queryCategoryById(pid);
        if(CollectionUtils.isEmpty(categories)) {
            return ResponseEntity.notFound().build(); // 响应 404
        }
        return ResponseEntity.ok(categories);
    }
}

4.3.6 测试
  1. 启动三个服务

不经过网关访问

经过网关访问

最后到后台管理系统的分类管理菜单

发现报错了

浏览器直接访问没事,但是这里却报错,什么原因?

这其实是浏览器的同源策略造成的跨域问题。

5. 跨域问题

5.1 跨域

跨域是浏览器对于 javascript 的同源策略的限制

以下情况都属于跨域:

域名和端口都相同,但是请求路径不同,则不属于跨域,如:

www.jd.com/item

www.jd.com/goods

5.2 跨域问题

跨域不一定都会有跨域问题。

跨域问题是浏览器对于 ajax 请求的一种安全限制:一个页面发起的 ajax 请求,只能是与当前页域名相同的路径,这能有效的阻止跨站攻击

但是这却给我们的开发带来了不便,而且在实际生产环境中,肯定会有很多台服务器之间交互,地址和端口都可能不同,下面就来解决跨域问题。

5.3 解决跨域问题的方案

目前比较常用的跨域解决方案有三种:

  • Jsonp

    最早的解决方案,利用 script 标签可以跨域的原理实现。

    缺点:

    • 需要服务的支持
    • 只能发起 GET 请求
  • Nginx 反向代理

    利用 Nginx 把跨域反向代理为不跨域,支持各种请求方式

    缺点:

    • 需要在 Nginx 进行额外配置,语义不清晰

CORS

规范化的跨域请求解决方案,安全可靠。

优点:

  • 在服务端进行控制是否允许跨域,可自定义规则
  • 支持各种请求方式

缺点:

  • 会产生额外的请求

5.4 CORS 解决跨域问题

5.4.1 CORS 简介

CORS 是一个 W3C 标准,全称是"跨域资源共享"(Cross-origin resource sharing)

它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 Ajax 只能同源使用的限制。

CORS 需要浏览器和服务器同时支持

  • 浏览器端

    目前,所有浏览器都支持该功能(IE10 以下不行)。整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。

  • 服务端

    CORS 通信与 AJAX 没有任何差别,因此你不需要改变以前的业务逻辑。但浏览器会在请求中携带一些头信息,我们需要以此判断是否允许其跨域,然后在响应头中加入一些信息即可。这一般通过过滤器完成即可。

5.4.2 CORS 原理

浏览器会将 Ajax 请求分为两类:

  • 简单请求
  • 特殊请求

两者处理方案略有差异

5.4.2.1 简单请求

简单请求的定义

满足以下两个条件,就属于简单请求:

  1. 请求方法是以下三种方法之一

    • HEAD
    • GET
    • POST

2.HTTP 的头信息不超出以下几种字段

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值 application/x-www-form-urlencodedmultipart/form-datatext/plain

发起简单请求

当浏览器发现发起的 Ajax 请求是简单请求时,会在请求头中携带一个字段 Origin,Origin 中会指出当前请求属于哪个域,服务会根据这个值决定是否允许其跨域。

简单请求的响应

如果服务器允许跨域,需要在返回的响应头中携带下面信息:

Access-Control-Allow-Origin: http://manage.leyou.com
Access-Control-Allow-Credentials: true
Content-Type: text/html; charset=utf-8
  • Access-Control-Allow-Origin:可接受的域,是一个具体域名或者 *(代表任意域名)
  • Access-Control-Allow-Credentials:是否允许携带 cookie,默认情况下,CORS 不会携带 cookie,除非这个值是 true

操作 cookie 的条件

要想操作 cookie,需要满足三个条件:

  • 服务的响应头中需要携带 Access-Control-Allow-Credentials 并且为 true。
  • 浏览器发起 ajax 需要指定 withCredentials 为 true
  • 响应头中的 Access-Control-Allow-Origin 一定不能为 *,必须是指定的域名
     
5.4.2.2 特殊请求

特殊请求的定义

不符合简单请求的条件,会被浏览器判定为特殊请求,例如请求方式为 PUT。

预检请求

特殊请求会在正式通信之前,增加一次 HTTP 查询请求,称为"预检"请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的 XMLHttpRequest 请求,否则就报错。

一个“预检”请求的样板:

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...

与简单请求相比,除了 Origin 以外,多了两个请求头:

  • Access-Control-Request-Method:接下来会用到的请求方式,比如 PUT
  • Access-Control-Request-Headers:会额外用到的头信息

预检请求的响应

服务收到预检请求,如果许可跨域,会发出响应:

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://manage.leyou.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Max-Age: 1728000
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

除了 Access-Control-Allow-Origin 和 Access-Control-Allow-Credentials 以外,这里又多出三个响应头:

  • Access-Control-Allow-Methods:允许访问的方式
  • Access-Control-Allow-Headers:允许携带的头
  • Access-Control-Max-Age:本次许可的有效时长,单位是秒

发起真实请求

如果浏览器得到上述响应,则认定为可以跨域,就可以发出真实请求了。

5.4.3 实现 CORS 解决跨域问题

实现思路

  • 浏览器端都有浏览器自动完成,我们无需操心
  • 服务端可以通过拦截器统一实现,不必每次都去进行跨域判定的编写。

事实上,SpringMVC 已经帮我们写好了 CORS 的跨域过滤器:CorsFilter,内部已经实现了刚才所讲的判定逻辑,我们直接用就好了。

实现

在 leyou-gateway 中编写一个配置类,并且注册 CorsFilter

@Configuration
public class LeyouCorsConfigration {

    @Bean
    public CorsFilter corsFilter() {
        // 初始化 cors 配置对象
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("http://manage.leyou.com"); //允许的域
        config.setAllowCredentials(true); //是否发送 Cookie 信息
        config.addAllowedMethod("*"); //允许的请求方式
        config.addAllowedHeader("*"); //允许的头信息

        //初始化 cors 配置源对象
        UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
        configSource.registerCorsConfiguration("/**", config); //添加映射路径,拦截一切请求

        return new CorsFilter(configSource); //返回 CorsFilter
    }
}

打开浏览器到后台管理系统的分类管理菜单,成功展示动态数据

6. 品牌的查询

6.1 实现展示静态数据

6.2 实现展示动态数据

6.2.1 url 异步请求

点击商品管理下的分类管理菜单,打开浏览器控制台

由此可以得到请求路径及参数

6.2.2 数据库
CREATE TABLE `tb_brand` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '品牌id',
  `name` varchar(50) NOT NULL COMMENT '品牌名称',
  `image` varchar(200) DEFAULT '' COMMENT '品牌图片地址',
  `letter` char(1) DEFAULT '' COMMENT '品牌的首字母',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=325400 DEFAULT CHARSET=utf8 COMMENT='品牌表,一个品牌下有多个商品(spu),一对多关系';

这里需要注意的是,品牌和商品分类之间是多对多关系,因此我们有一张中间表:

CREATE TABLE `tb_category_brand` (
  `category_id` bigint(20) NOT NULL COMMENT '商品类目id',
  `brand_id` bigint(20) NOT NULL COMMENT '品牌id',
  PRIMARY KEY (`category_id`,`brand_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品分类和品牌的中间表,两者是多对多关系';

但是,你可能会发现,这张表中并没有设置外键约束,似乎与数据库的设计范式不符。为什么这么做?

  • 外键会严重影响数据库读写的效率
  • 数据删除时会比较麻烦
6.2.3 实体类
@Table(name = "tb_brand")
public class Brand {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String image;
    private Character letter;

	// 构造器、getter 和 setter 方法、toString 方法省略
}

6.2.4 Mapper
public interface BrandMapper extends Mapper<Brand> {
}
6.2.5 Service

编写 Service 之前,先看看前端给我们发来的请求参数和要得到的响应结果:

由此我们可以得知:

  • 请求方式:Get
  • 请求路径:/item/brand/page
  • 请求参数:根据我们刚才编写的页面,有分页功能,有排序功能,有搜索功能
    • page:当前页,int
    • rows:每页大小,int
    • sortBy:排序字段,String
    • desc:是否为降序,boolean
    • key:搜索关键词,String
  • 响应结果:分页结果一般至少需要两个数据
    • total:总条数
    • items:当前页数据
    • totalPage:有些还需要总页数

分页结果类

由响应结果可知,我们还需要一个分页结果类。另外,这个类以后可能在其它项目中也有需求,因此我们将其抽取到 leyou-common 中。

package com.leyou.common.pojo;

import java.util.List;

public class PageResult<T> {
    private Long total; //总条数
    private List<T> items; //当前页数据
    private Integer totalPage; //总页数

    public PageResult() {
    }

    public PageResult(Long total, List<T> items) {
        this.total = total;
        this.items = items;
    }

    public PageResult(Long total, List<T> items, Integer totalPage) {
        this.total = total;
        this.items = items;
        this.totalPage = totalPage;
    }

	// getter 和 setter 方法、toString 方法省略
}

在 leyou-item-service 中引入 leyou-common 的依赖

<dependency>
    <groupId>com.leyou.common</groupId>
    <artifactId>leyou-common</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

编写 service

@Service
public class BrandService {
    @Autowired
    private BrandMapper brandMapper;

    /**
     * 根据查询条件分页并排序查询品牌信息
     *
     * @param key    搜索关键词
     * @param page   当前页
     * @param rows   每页大小
     * @param sortBy 排序字段
     * @param desc   是否为降序
     * @return
     */
    public PageResult<Brand> queryBrandsByPage(String key, Integer page, Integer rows, String sortBy, Boolean desc) {
        // 初始化 example 对象
        Example example = new Example(Brand.class);
        Example.Criteria criteria = example.createCriteria();

        // 根据 name 模糊查询,或根据 letter 查询
        if (StringUtils.isNotBlank(key)) {
            criteria.andLike("name", "%" + key + "%").orEqualTo("letter", key);
        }

        // 设置分页条件
        PageHelper.startPage(page, rows);

        // 添加排序
        if (StringUtils.isNotBlank(sortBy)) {
            example.setOrderByClause(sortBy + " " + (desc ? "desc" : "asc"));
        }

        List<Brand> brands = brandMapper.selectByExample(example);
        // 包装成 pageInfo
        PageInfo<Brand> brandPageInfo = new PageInfo<>(brands);

        // 包装成分页结果集返回
        return new PageResult<Brand>(brandPageInfo.getTotal(), brandPageInfo.getList());
    }
}

6.2.6 Controller
@RestController
@RequestMapping("/brand")
public class BrandController {
    @Autowired
    private BrandService brandService;
	
	/**
     * 根据查询条件分页并排序查询品牌信息
     * @param key    搜索关键词
     * @param page   当前页
     * @param rows   每页大小
     * @param sortBy 排序字段
     * @param desc   是否为降序
     * @return
     */
    @GetMapping("/page")
    public ResponseEntity<PageResult<Brand>> queryBrandsByPage(
            @RequestParam(value = "key", required = false) String key,
            @RequestParam(value = "page", defaultValue = "1") Integer page,
            @RequestParam(value = "rows", defaultValue = "5") Integer rows,
            @RequestParam(value = "sortBy", required = false) String sortBy,
            @RequestParam(value = "desc", required = false) Boolean desc
    ) {
        PageResult<Brand> brandPageResult = brandService.queryBrandsByPage(key, page, rows, sortBy, desc);
        if (CollectionUtils.isEmpty(brandPageResult.getItems())) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(brandPageResult);
    }
}

6.2.7 测试

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

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

相关文章

LeakCanary(4)面试题系列

序、慢慢来才是最快的方法。 问题1&#xff1a;LeakCanary 支持Android 场景中的那些内存泄漏监测&#xff1f; 已销毁的 Activity 对象&#xff08;进入 DESTROYED 状态&#xff09;&#xff1b;已销毁的 Fragment 对象和 Fragment View 对象&#xff08;进入 DESTROYED 状态…

面试算法25:链表中的数字相加

题目 给定两个表示非负整数的单向链表&#xff0c;请问如何实现这两个整数的相加并且把它们的和仍然用单向链表表示&#xff1f;链表中的每个节点表示整数十进制的一位&#xff0c;并且头节点对应整数的最高位数而尾节点对应整数的个位数。例如&#xff0c;两个分别表示整数98…

css吸顶特效(elementui vue3官网)

效果如图&#xff1a;当浏览器滚轮在最上面的时候 没什么区别。当鼠标滚轮超出最上面高度时会有这种粒子感。吸顶遮盖下面内容 首先要 明确 css 基础属性 position: sticky;的用法。再了解 background-image: radial-gradient(transparent 1px, #fff 1px); background-size: …

Java 8 引进的一个新特性 Optional

Optional 是 Java 8 引进的一个新特性&#xff0c;通常用于缓解常见的空指针异常问题。 Brian Goetz &#xff08;Java语言设计架构师&#xff09;对Optional设计意图的原话如下&#xff1a; Optional is intended to provide a limited mechanism for library method return…

马蹄集matji oj赛(第十二次)

目录 元素共鸣 欧拉函数 欧拉函数2 小码哥的喜欢数 整数的逆 数的自我 阶乘的质因子 分数个数 质数率 数字游戏 元素共鸣 难度&#xff1a;黄金 0时间限制&#xff1a;1秒 巴占用内存&#xff1a;128M 遥远的大陆上存在着元素共鸣的机制。 建立一个一维坐标系&#x…

保护隐私与增强网络安全之网络代理技术

目录 前言 一、网络代理技术原理 二、网络代理技术类型 1. HTTP代理 2. SOCKS代理 3. DNS代理 4. 加密代理 5. 反向代理 三、网络代理技术应用 1. 加速网络访问速度 2. 绕过网络限制 3. 保护个人隐私 4. 节省带宽 5. 改善网络安全 四、网络代理技术优缺点 网络…

APK大小缩小65%,内存减少70%:如何优化Android App

APK大小缩小65&#xff05;&#xff0c;内存减少70&#xff05;&#xff1a;如何优化Android App 我们一直在努力为我们的Android应用程序构建MVP产品。在开发MVP产品后&#xff0c;我们发现需要进行应用程序优化以提高性能。经过分析&#xff0c;我们发现了以下可以改进的应用…

比特币有助减少腐败;微软 Copilot 每月赔 20 美元;AIGC 明年会“洗冷水澡”丨 RTE 开发者日报 Vol.64

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE &#xff08;Real Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

AMEYA360分享:村田电子搭载了Onsemi公司IoT设备专用IC的新Bluetooth® Low Energy模块开始量产

近年来&#xff0c;所有远程监控、远程控制的用例均要求具备可无线连接的电池驱动IoT设备&#xff0c;而长寿命电池与安全的数据通信功能是其关键。为此&#xff0c;在IoT边缘设备的设计方面&#xff0c;最大的课题是要提高功率效率和安全性。 Type 2EG由于无线与内置微处理器两…

React 状态管理 - Mobx 入门(下)接入实战

目录 Mobx接入实战 Mobx构造复杂应用需要注意的 Mobx5 Or Mobx4 Mobx5 Mobx4 /package.json /src/routes/index.jsx /src/app.jsx /src/index.jsx /src/models/home/index.js /src/models/index.js /src/containers/home/index.jsx Mobx VS Redux Mobx接入实战 对…

RabbitMQ之延迟队列解读

目录 基本介绍 概述 为什么需要引进RabbitMQ延迟队列 应用场景 springboot代码实战 实战架构 工程概述 RabbitConfigDeal 配置类&#xff1a;创建队列及交换机并进行绑定 MessageService业务类&#xff1a;发送消息及接收消息 主启动类RabbitMq01Application&#xff1…

2023年中国医院信息系统发展现状及行业市场规模分析[图]

医院信息系统&#xff0c;亦称“医院管理信息系统”&#xff08;简称HIS&#xff09;&#xff0c;是指利用计算机软硬件技术、网络通信技术等现代化手段&#xff0c;对医院及其所属各部门的人流、物流、财流进行综合管理&#xff0c;对在医疗活动各阶段产生的数据进行采集储存、…

Lab 1: Unix utilities汇总

这个实验主要学习了常用的一些系统调用。 Lab 1: Unix utilities Boot xv6 (easy) git克隆&#xff0c;切换分支&#xff0c;qemu。根据要求进行操作即可。 $ git clone git://g.csail.mit.edu/xv6-labs-2020 $ cd xv6-labs-2020 $ git checkout util $ make qemusleep (ea…

分享一下花店制作微信小程序的步骤是什么

一、准备阶段 在准备阶段&#xff0c;花店需要完成以下任务&#xff1a; 注册微信公众平台账号&#xff1a;首先&#xff0c;花店需要注册一个微信公众平台账号&#xff0c;这个账号将用于创建和管理微信小程序。 确定小程序的功能和需求&#xff1a;花店需要根据自身的业务需…

RISC-V架构 | 飞凌嵌入式FET7110-C国产高性能核心板现货发售!

RISC-V凭借其完全开源免费且可自由修改的特性而备受国内厂商的追捧&#xff0c;在此背景下&#xff0c;飞凌嵌入式联合RISC-V国产处理器厂商赛昉科技(StarFive)基于昉惊鸿7110处理器共同推出FET7110-C核心板。 现在&#xff0c;飞凌嵌入式FET7110-C核心板&#xff08;商业级&a…

2023.10月网络优化项目实战

基础配置 sw2 <Huawei>sy Enter system view, return user view with Ctrl+Z. [Huawei]sy sw2 [sw2]vlan batch 10 20 Info: This operation may take a few seconds. Please wait for a moment...done.[sw2]int e0/0/1 [sw2-Ethernet0/0/1]port link-type access [s…

第十章-输入输出系统

Ⅰ.锁 本质是互斥操作 原因&#xff1a;针对公共资源访问时&#xff0c;临界区若不加以互斥限制&#xff0c;可能导致执行过程中突然的中断导致出现异常。 1.互斥过程 设定互斥量M为二值信号量&#xff0c;0/1&#xff0c;P-&#xff0c;V&#xff0c;现有两个进程A、B共同…

大数据flink篇之三-flink运行环境安装(一)单机Standalone安装

一、安装包下载地址 https://archive.apache.org/dist/flink/flink-1.15.0/ 二、安装配置流程 前提基础&#xff1a;Centos环境&#xff08;建议7以上&#xff09; 安装命令&#xff1a; 解压&#xff1a;tar -zxvf flink-xxxx.tar.gz 修改配置conf/flink-conf.yaml&#xff1…

最新AI创作系统源码ChatGPT网站源码/支持Midjourney,AI绘画/支持OpenAI GPT全模型+国内AI全模型

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如…

RxJava介绍及基本原理

随着互联网的迅猛发展&#xff0c;Java已成为最广泛应用于后端开发的语言之一。而在处理异步操作和事件驱动编程方面&#xff0c;传统的Java多线程并不总是最佳选择。这时候&#xff0c;RxJava作为一个基于观察者模式、函数式编程和响应式编程理念的库&#xff0c;为我们提供了…