自营性电商项目④

类别管理–根据id查询类别详情–持久层

13.1. 规划SQL语句

本次需要执行的SQL语句大致是:

select * from pms_category where id=?

关于字段列表,应该包括:

id, name, parent_id, depth, keywords, sort, icon, enable, is_parent, is_display

13.2. 抽象方法(可能需要创建VO类)

在csmall-pojo的根包下的vo包下创建CategoryDetailsVO类,封装以上设计的字段对应的属性:

import lombok.Data;import java.io.Serializable;@Datapublic class CategoryDetailsVO implements Serializable { private Long id; private String name; private Long parentId; private Integer depth; private String keywords; private Integer sort; private String icon; private Integer enable; private Integer isParent; private Integer isDisplay;}

在CategoryMapper接口中添加:

CategoryDetailsVO getDetailsById(Long id);

13.3. 在XML中配置SQL

在CategoryMapper.xml中添加配置:

select from pms_category where id=#{id} id, name, parent_id, depth, keywords, sort, icon, enable, is_parent, is_display

13.4. 测试

@Test@Sql({“classpath:truncate.sql”, “classpath:insert_data.sql”})public void testGetDetailsByIdSuccessfully() { // 测试数据 Long id = 1L; // 断言不会抛出异常 assertDoesNotThrow(() -> { // 执行查询 Object category = mapper.getDetailsById(id); // 断言查询结果不为null assertNotNull(category);});}@Test@Sql({“classpath:truncate.sql”})public void testGetDetailsByIdFailBecauseNotFound() { // 测试数据 Long id = -1L; // 断言不会抛出异常 assertDoesNotThrow(() -> { // 执行查询 Object category = mapper.getDetailsById(id); // 断言查询结果为null assertNull(category);});}

14. 类别管理–根据id查询类别详情–业务逻辑层

14.1. 接口和抽象方法

在ICategoryService中添加:

CategoryDetailsVO getDetailsById(Long id);

14.2. 实现

在CategoryServiceImpl中执行查询并返回。

14.3. 测试

@Test@Sql({“classpath:truncate.sql”, “classpath:insert_data.sql”})public void testGetDetailsByIdSuccessfully() { // 测试数据 Long id = 1L; // 断言不抛出异常 assertDoesNotThrow(() -> { service.getDetailsById(id); });}@Test@Sql({“classpath:truncate.sql”})public void testGetDetailsByIdFailBecauseNotFound() { // 测试数据 Long id = -1L; // 断言抛出异常 assertThrows(ServiceException.class, () -> { service.getDetailsById(id); });}

15. 类别管理–根据id查询类别详情–控制器层

在CategoryController中添加:

@GetMapping(“/{id}”)public JsonResult getDetailsById(@PathVariable Long id) { CategoryDetailsVO category = categoryService.getDetailsById(id); return JsonResult.ok(category);}

在CategoryControllerTests中测试:

@Test@Sql({“classpath:truncate.sql”, “classpath:insert_data.sql”})public void testGetDetailsByIdSuccessfully() throws Exception { // 准备测试数据,注意:此次没有提交必要的name属性值 String id = “1”; // 请求路径,不需要写协议、服务器主机和端口号 String url = “/categories/” + id; // 执行测试 // 以下代码相对比较固定 mockMvc.perform( // 执行发出请求 MockMvcRequestBuilders.get(url) // 根据请求方式决定调用的方法 .contentType(MediaType.APPLICATION_FORM_URLENCODED) // 请求数据的文档类型,例如:application/json; charset=utf-8 .accept(MediaType.APPLICATION_JSON)) // 接收的响应结果的文档类型,注意:perform()方法到此结束 .andExpect( // 预判结果,类似断言 MockMvcResultMatchers .jsonPath(“state”) // 预判响应的JSON结果中将有名为state的属性 .value(State.OK.getValue())) // 预判响应的JSON结果中名为state的属性的值,注意:andExpect()方法到此结束 .andDo( // 需要执行某任务 MockMvcResultHandlers.print()); // 打印日志}@Test@Sql({“classpath:truncate.sql”, “classpath:insert_data.sql”})public void testGetDetailsByIdFailBecauseNotFound() throws Exception { // 准备测试数据,注意:此次没有提交必要的name属性值 String id = “9999999999”; // 请求路径,不需要写协议、服务器主机和端口号 String url = “/categories/” + id; // 执行测试 // 以下代码相对比较固定 mockMvc.perform( // 执行发出请求 MockMvcRequestBuilders.get(url) // 根据请求方式决定调用的方法 .contentType(MediaType.APPLICATION_FORM_URLENCODED) // 请求数据的文档类型,例如:application/json; charset=utf-8 .accept(MediaType.APPLICATION_JSON)) // 接收的响应结果的文档类型,注意:perform()方法到此结束 .andExpect( // 预判结果,类似断言 MockMvcResultMatchers .jsonPath(“state”) // 预判响应的JSON结果中将有名为state的属性 .value(State.ERR_CATEGORY_NOT_FOUND.getValue())) // 预判响应的JSON结果中名为state的属性的值,注意:andExpect()方法到此结束 .andDo( // 需要执行某任务 MockMvcResultHandlers.print()); // 打印日志}

管理员相关数据表

管理员及权限的管理,涉及的数据表有:

— 数据库:mall_ams– 权限表:创建数据表drop table if exists ams_permission;create table ams_permission ( id bigint unsigned auto_increment, name varchar(50) default null comment ‘名称’, value varchar(255) default null comment ‘值’, description varchar(255) default null comment ‘描述’, sort tinyint unsigned default 0 comment ‘自定义排序序号’, gmt_create datetime default null comment ‘数据创建时间’, gmt_modified datetime default null comment ‘数据最后修改时间’, primary key (id)) comment ‘权限表’ charset utf8mb4;– 权限表:插入测试数据insert into ams_permission (name, value, description) values(‘商品-商品管理-读取’, ‘/pms/product/read’, ‘读取商品数据,含列表、详情、查询等’),(‘商品-商品管理-编辑’, ‘/pms/product/update’, ‘修改商品数据’),(‘商品-商品管理-删除’, ‘/pms/product/delete’, ‘删除商品数据’),(‘后台管理-管理员-读取’, ‘/ams/admin/read’, ‘读取管理员数据,含列表、详情、查询等’),(‘后台管理-管理员-编辑’, ‘/ams/admin/update’, ‘编辑管理员数据’),(‘后台管理-管理员-删除’, ‘/ams/admin/delete’, ‘删除管理员数据’);– 角色表:创建数据表drop table if exists ams_role;create table ams_role ( id bigint unsigned auto_increment, name varchar(50) default null comment ‘名称’, description varchar(255) default null comment ‘描述’, sort tinyint unsigned default 0 comment ‘自定义排序序号’, gmt_create datetime default null comment ‘数据创建时间’, gmt_modified datetime default null comment ‘数据最后修改时间’, primary key (id)) comment ‘角色表’ charset utf8mb4;– 角色表:插入测试数据insert into ams_role (name) values (‘超级管理员’), (‘系统管理员’), (‘商品管理员’), (‘订单管理员’);– 角色权限关联表:创建数据表drop table if exists ams_role_permission;create table ams_role_permission ( id bigint unsigned auto_increment, role_id bigint unsigned default null comment ‘角色id’, permission_id bigint unsigned default null comment ‘权限id’, gmt_create datetime default null comment ‘数据创建时间’, gmt_modified datetime default null comment ‘数据最后修改时间’, primary key (id)) comment ‘角色权限关联表’ charset utf8mb4;– 角色权限关联表:插入测试数据insert into ams_role_permission (role_id, permission_id) values (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (2, 6), (3, 1), (3, 2), (3, 3);– 管理员表:创建数据表drop table if exists ams_admin;create table ams_admin ( id bigint unsigned auto_increment, username varchar(50) default null unique comment ‘用户名’, password char(64) default null comment ‘密码(密文)’, nickname varchar(50) default null comment ‘昵称’, avatar varchar(255) default null comment ‘头像URL’, phone varchar(50) default null unique comment ‘手机号码’, email varchar(50) default null unique comment ‘电子邮箱’, description varchar(255) default null comment ‘描述’, is_enable tinyint unsigned default 0 comment ‘是否启用,1=启用,0=未启用’, last_login_ip varchar(50) default null comment ‘最后登录IP地址(冗余)’, login_count int unsigned default 0 comment ‘累计登录次数(冗余)’, gmt_last_login datetime default null comment ‘最后登录时间(冗余)’, gmt_create datetime default null comment ‘数据创建时间’, gmt_modified datetime default null comment ‘数据最后修改时间’, primary key (id)) comment ‘管理员表’ charset utf8mb4;– 管理员表:插入测试数据insert into ams_admin (username, password, nickname, email, description, is_enable) values (‘root’, ‘1234’, ‘root’, ‘root@celinf.cn’, ‘最高管理员’, 1), (‘super_admin’, ‘1234’, ‘administrator’, ‘admin@celinf.cn’, ‘超级管理员’, 1), (‘nobody’, ‘1234’, ‘无名’, ‘liucs@celinf.cn’, null, 0);– 管理员角色关联表:创建数据表drop table if exists ams_admin_role;create table ams_admin_role ( id bigint unsigned auto_increment, admin_id bigint unsigned default null comment ‘管理员id’, role_id bigint unsigned default null comment ‘角色id’, gmt_create datetime default null comment ‘数据创建时间’, gmt_modified datetime default null comment ‘数据最后修改时间’, primary key (id)) comment ‘管理员角色关联表’ charset utf8mb4;– 管理员角色关联表:插入测试数据insert into ams_admin_role (admin_id, role_id) values (1, 1), (1, 2), (1, 3), (2, 2), (2, 3), (2, 4), (3, 3);– 查询示例:查询id=1的管理员的权限select distinct ams_permission.value from ams_permissionleft join ams_role_permission on ams_role_permission.permission_id=ams_permission.idleft join ams_role on ams_role_permission.role_id=ams_role.idleft join ams_admin_role on ams_admin_role.role_id=ams_role.idleft join ams_admin on ams_admin_role.admin_id=ams_admin.idwhere ams_admin.id=1order by ams_permission.value;– 管理员登录日志表:创建数据表drop table if exists ams_login_log;create table ams_login_log ( id bigint unsigned auto_increment, admin_id bigint unsigned default null comment ‘管理员id’, username varchar(50) default null comment ‘管理员用户名(冗余)’, nickname varchar(50) default null comment ‘管理员昵称(冗余)’, ip varchar(50) default null comment ‘登录IP地址’, user_agent varchar(255) default null comment ‘浏览器内核’, gmt_login datetime default null comment ‘登录时间’, gmt_create datetime default null comment ‘数据创建时间’, gmt_modified datetime default null comment ‘数据最后修改时间’, primary key (id)) comment ‘管理员登录日志表’ charset utf8mb4;– 管理员登录日志表:插入测试数据insert into ams_login_log (admin_id, username, nickname, ip, user_agent, gmt_login) values (1, ‘root’, ‘root’, ‘127.0.0.1’, ‘Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15’, DATE_SUB(NOW(), interval 1 day)), (2, ‘root’, ‘root’, ‘127.0.0.1’, ‘Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15’, DATE_SUB(NOW(), interval 12 hour)), (3, ‘root’, ‘root’, ‘127.0.0.1’, ‘Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15’, NOW());– 查看数据表结构desc ams_permission; desc ams_role; desc ams_role_permission; desc ams_admin; desc ams_admin_role; desc ams_login_log;

当某个管理员尝试登录时,必须实现”根据用户名查询此管理员的信息,至少包括id、密码、权限“,需要执行的SQL语句大致是:

— 管理员表 admin– 角色表 role– 管理员与角色关联表 admin_role (admin_id, role_id)– 权限表 permission– 角色与权限关联表 role_permission (role_id, permission_id)– 【根据用户名查询管理员,且必须查出对应的权限】select ams_admin.id, ams_admin.username, ams_admin.password, ams_admin.is_enable, ams_permission.valuefrom ams_adminleft join ams_admin_role on ams_admin.id = ams_admin_role.admin_idleft join ams_role_permission on ams_admin_role.role_id = ams_role_permission.role_idleft join ams_permission on ams_role_permission.permission_id = ams_permission.idwhere username=’root’;

接下来,在根项目中创建csmall-admin模块(与csmall-product类似),并在其下创建csmall-admin-service和csmall-admin-webapi这2个子模块(与csmall-product的2个子模块类似),然后,尽量在csmall-admin-webapi中实现以上查询功能:

public interface AdminMapper { AdminLoginVO findByUsername(String username);}

最后,关于Key的使用,通常建议使用冒号区分多层次,类似URL的设计方式,例如:

  • 类别列表的Key:categories:list或categories
  • 某个id(9000)对应的类别的Key:categories:item:9000

关于用户身份认证与授权

Spring Security是用于解决认证与授权的框架。

在根项目下创建新的`csmall-passport`子模块,最基础的依赖项包括`spring-boot-starter-web`与`spring-boot-starter-security`(为避免默认存在的测试类出错,应该保留测试的依赖项`spring-boot-starter-test`),完整的`csmall-passwort`的`pom.xml`为:

org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-test test

调整完成后,即可启动项目,在启动的日志中,可以看到类似以下内容:

Using generated security password: 2abb9119-b5bb-4de9-8584-9f893e4a5a92

Spring Security有默认登录的账号和密码(以上提示的值),密码是随机的,每次启动项目都会不同。

Spring Security默认要求所有的请求都是必须先登录才允许的访问,可以使用默认的用户名`user`和自动生成的随机密码来登录。在测试登录时,在浏览器访问当前主机的任意网址都可以(包括不存在的资源),会自动跳转到登录页(是由Spring Security提供的,默认的URL是:http://localhost:8080/login),当登录成功后,会自动跳转到此前访问的URL(跳转登录页之前的URL),另外,还可以通过 http://localhost:8080/logout 退出登录。

Spring Security的依赖项中包括了Bcrypt算法的工具类,Bcrypt是一款非常优秀的密码加密工具,适用于对需要存储下来的密码进行加密处理。

import org.junit.jupiter.api.Test;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;public class BcryptPasswordEncoderTests { private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); @Test public void testEncode() { // 原文相同的情况,每次加密得到的密文都不同 for (int i = 0; i < 10; i++) { String rawPassword = "123456"; String encodedPassword = passwordEncoder.encode(rawPassword); System.out.println("rawPassword = " + rawPassword); System.out.println("encodedPassword = " + encodedPassword); } // rawPassword = 123456 // encodedPassword = $2a$10$HWuJ9WgPazrwg9.isaae4u7XdP7ohH7LetDwdlTWuPC4ZAvG.Uc7W // encodedPassword = $2a$10$rOwgZMpDvZ3Kn7CxHWiEbeC6bQMGtfX.VYc9DCzx9BxkWymX6FbrS } @Test public void testMatches() { String rawPassword = "123456"; String encodedPassword = "$2a$10$hI4wweFOGJ7FMduSmCjNBexbKFOjYMWl8hkug0n0k1LNR5vEyhhMW"; boolean matchResult = passwordEncoder.matches(rawPassword, encodedPassword); System.out.println("match result : " + matchResult); }}

如果要使得Spring Security能使用数据库中的信息(数据库中的用户名与密码)来验证用户身份(认证),首先,必须实现“根据用户名查询此用户的登录信息(应该包括权限信息)”的查询功能,要实现此查询,需要执行的SQL语句大致是:

selectams_admin.id, ams_admin.username, ams_admin.password, ams_admin.is_enable, ams_permission.valuefrom ams_admin left join ams_admin_role on ams_admin.id = ams_admin_role.admin_id left join ams_role_permission on ams_admin_role.role_id = ams_role_permission.role_id left join ams_permission on ams_role_permission.permission_id = ams_permission.idwhere username=’root’;

要在当前模块(`csmall-passport`)中实现此查询功能,需要:

  • – [`csmall-passport`] 添加数据库编程的相关依赖
  • – `mysql-connector-java`
  • – `mybatis-spring-boot-starter`
  • – `durid` / `druid-spring-boot-starter`
  • – [`csmall-passport`] 添加连接数据库的配置信息
  • – [`csmall-passport`] 创建`MybatisConfiguration`配置类,用于配置`@MapperScan`
  • – [`csmall-passport`] 在配置文件中配置`mybatis.mapper-locations`属性,以指定XML文件的位置
  • – [`csmall-pojo`] 创建`AdminLoginVO`类
  • @Datapublic class AdminLoginVO implements Serializable { private Long id; private String username; private String password; private Integer isEnable; private List permissions;}

    – [`csmall-passport`] 在`pom.xml`中添加对`csmall-pojo`的依赖

    – [`csmall-passport`] 在`src/main/java`下的`cn.celinf.csmall.passport`包下创建`mapper.AdminMapper.java`接口

    – [`csmall-passport`] 在接口中添加抽象方法:

    AdminLoginVO getLoginInfoByUsername(String username);

    – 在`src/main/resources`下创建`mapper`文件夹,并在此文件夹下粘贴得到`AdminMapper.xml`

    – 在`AdminMapper.xml`中配置以上抽象方法映射的SQL查询:

    selectfrom ams_adminleft join ams_admin_roleon ams_admin.id = ams_admin_role.admin_idleft join ams_role_permissionon ams_admin_role.role_id = ams_role_permission.role_idleft join ams_permissionon ams_role_permission.permission_id = ams_permission.idwhere username=#{username}ams_admin.id,ams_admin.username,ams_admin.password,ams_admin.is_enable,ams_permission.value

    – 完成后,还应该编写并执行测试

    根据有效的用户名查询出的结果例如:

    AdminLoginVO( id=1, username=root, password=1234, isEnable=1, permissions=[ /pms/product/read, /pms/product/update, /pms/product/delete, /ams/admin/read, /ams/admin/update, /ams/admin/delete ])

    学习记录,如有侵权请联系删除

    郑重声明:本文内容及图片均整理自互联网,不代表本站立场,版权归原作者所有,如有侵权请联系管理员(admin#wlmqw.com)删除。
    (0)
    用户投稿
    上一篇 2022年6月18日
    下一篇 2022年6月18日

    相关推荐

    联系我们

    联系邮箱:admin#wlmqw.com
    工作时间:周一至周五,10:30-18:30,节假日休息