目录
3.2.5仅实现用户登录和token校验接口(未连接数据库)
本篇代码地址: Gitee 地址 注意是 project01 分支哦
使用IDEA打开项目时,及机的使用 import ,而不是 open 哦,并且 .idea 和 .iml 文件如果存在的话记得要先删除他俩,然后再import!
1.平台说明
想自己开发一个项目,熟悉一下项目流程,打算从项目的整个流程开始分析,包括【设计】、【研发】、【测试】。
项目暂定包含几大模块,从用户使用角度:用户模块、博客模块、商品模块、交易模块。
其实就是基本实现多用户下的博客系统和商城系统。
2.基础框架设计思路
现在先从【设计】角度出发,先分析基本的需求,一个网站最重要的就是分析清楚网站的用户属性,即账号类型;然后再根据具体业务转化为具体功能。
2.1用户模块思路
那么先从网站的用户属性来分析,当用户数据逻辑清晰之后,就可以搭建最基本的用户模块。给予用户或账号信息再逐步搭建后面的模块,当然也可以先搭建业务模块,但是无论是那种先行都需要分析清楚使用的用户属性。
网上流传的商业模式都有:B2B , B2C , C2C , B2B2C ,这里就不一一介绍了,我打算使用最后一个也就是 B2B2C 模式,这个网站平台即为组织/企业提供服务,也为个人提供服务。就类似于下图:
平台里面有多个个人帐号,多个组织以及每个组织里面包含多个子账号。
组织就是第一个B,指的是商品或服务的供应商,俗称卖方;平台就是第二个B,指的是提供卖方与买方的联系平台,也可以提供服务;个人就是C,指的是个人消费者,俗称买方。
由此我们会产生几个疑问:
1.个人帐号是否能够成为组织账号?
2.如果能,那么个人账号是否可以成为多个组织的子账号?如果是,那么登陆时怎样确认当前登陆的是哪个呢?
3.由于组织账号下还会有子账号,那么子帐号又是怎样区分呢?
解答:
解答之前,我们先想一下阿里云、腾讯云是怎样的解决的?这两个云都提供了账号的注册,注册完毕后可以选择认证方式:个人认证、企业认证;无论是哪种认证方式,都可以在当前账号下创建子账号,子账号可以使用用户名的方式。账号和子账号的登录方式是不同的。
上面的是一种方式,即,每个账号之间是完全区分开的,其子账号只与当前账号有关,即便有多个子账号绑定的手机号码或者邮箱是同一个,但是登录时会判断当前登录的是账号还是某个账号的子账号,登陆成功再根据账号类型判断其余业务数据。
还有另外一种方式,我公司的哥说这种才是互联网平台的账号思维,即,用户注册账号后就拥有个人账号,他可以申请创建组织账号,申请之后会再拥有一个组织账号并成为其创建人,然后可以邀请其他账号加入组织,这个其他账号就是某个个人账号。
也就是说一个账号可以登录个人账号,也可以选择登录某个组织的账号。
上面这两种方式的唯一一个区别就是:
第一种:一个账号对应一个集合(个人或组织)信息,若想要操作多个集合信息,需要在对应的集合重新申请账号,并且如果要切换集合,只能退出重新登陆;
第二种:一个账号可以对应多个组织信息,若想要操作多个组织信息,直接加入组织就可以,只需要在登录的时候选择当前操作的是哪个组织,登录期间也可以切换组织;
按照用户操作方便来说第二种会更加简便,省去了很多使用步骤!但是,这种对用户来说又很容易产生数据错误,一旦用户忘记当前操作的组织数据,就很有可能将A组织的数据添加到B组织中,或者使用A组织的身份对B组织产生交互数据!
所以这里就采用第一种方式吧,后续看有没有时间改进成第二种方式的用户模块!
所以,我们的平台由三种用户类型组成:
1.平台管理级别用户:只能登陆平台后台管理系统,操作平台级别的业务;
2.平台使用用户:只能登陆平台前台管理系统(包括用户前台、用户后台),操作平台前台的业务,具体业务权限由平台管理级别用户管理(大部分是用户自己决定开通与否);
3.平台使用子用户:是属于平台使用用户的子用户,业务功能和平台使用用户一样,但是具体的权限由所属使用账号决定;
2.2用户模块业务功能接口
由于正常的项目流程是需要设计师出原型图+业务描述的,然后研发人员根据原型图和业务,设计功能对应的接口。
但是画图太麻烦了,所以就先将基础的功能接口描述出来啦!
(这里仅仅只作为接口的简单描述,具体的在后面再生成接口文档。正常来说研发人员会根据原型图考虑出大概的接口)
序号 | 接口 | 接口描述 |
1 | 注册 | 用户名、密码、手机号码等 |
登录 | 用户名密码登录、手机号码登录 | |
账号信息 | 获取 | |
账号信息 | 修改 | |
修改密码 | 通过原密码 | |
找回密码 | 通过手机号码 | |
菜单列表 | 获取列表 | |
菜单列表 | 获取一条信息 | |
菜单列表 | 新增一个菜单,类型:目录、菜单、按钮,目录仅表示由下一级,菜单表示有页面,按钮表示页面里的按钮; | |
菜单列表 | 修改 | |
菜单列表 | 删除 | |
部门列表 | 获取列表 | |
部门列表 | 获取一条信息 | |
部门列表 | 获取树形下拉列表,单选框 | |
部门列表 | 新增 | |
部门列表 | 修改 | |
部门列表 | 删除 | |
角色列表 | 获取列表 | |
角色列表 | 获取一条信息 | |
角色列表 | (获取菜单树形下拉框,多选框) | |
角色列表 | 新增 | |
角色列表 | 修改 | |
角色列表 | 删除 | |
角色列表 | (获取部门树形下拉,多选框) | |
角色列表 | 修改数据权限(全部、个人、自定义部门.etc) | |
角色列表 | 获取角色下的成员列表 | |
角色列表 | (获取不是某角色的成员列表) | |
角色列表 | 添加成员 | |
角色列表 | 删除成员 | |
用户列表 | 获取列表 | |
用户列表 | 获取一条信息 | |
用户列表 | (获取角色下拉列表,单选框) | |
用户列表 | (获取部门树形下拉列表,单选框) | |
用户列表 | 新增 | |
用户列表 | 修改 | |
用户列表 | 删除 | |
用户列表 | 重置密码 |
大概就有这些接口,我们先将基本的用户账号完成,再完成菜单、部门、角色,和最后的用户信息。
2.3用户模块接口详细设计
正常情况下前后端分离项目,开始研发前会先设计接口的详细设计,也就是接口的入参、出参、请求方式、接口名、头部信息等的设计,出参、入参一般是根据原型图中需要的信息提取出来的,状态值一般是由后端给出的。
由于我们没有原型图,所以只设计最基本的数据就行,例如账号里面可能会有邮箱啊、地址啊等信息,但是我们可以先不加仙剑能用到的加上,比如手机号码、用户名,这些必须的字段加上。后面的设计到可以补充,当然如果后面能用到的话,也可以一开始就加上。
接口详细文档不打算写表格了,太麻烦,我直接使用 Apipost工具来保存,同时用这个工具测试后端接口,这个还挺方便的,用Postman也可以,功能差不多,都能够保存接口属性并测试接口,然后还可以将接口文档分享出来。
3.基础框架研发思路
开发项目时了解业务之后,先不着急下手,先确定是用什么工具,然后搭建一下基本架构。
本来想用springcloud搭建分布式的,但是因为初学还不熟悉而且笔记本跑三四个项目跑不起来(唉,该也该让他休息了...),所以还是用 springboot 单项目吧,后面再拆。
3.1基础框架思考
3.1.1技术选型
项目整体是按照若依框架—(一个超棒的框架,比较适合springboot的入门思考)的逻辑搭建的,那么目前我们使用的技术和若依框架的技术是相似的:
开发工具:IDEA ,VSCode,Mysql
1、系统环境
- Java EE 8
- Servlet 3.0
- Apache Maven 3
2、主框架
- Spring Boot 2.2.x
- Spring Framework 5.2.x
- Spring Security 5.2.x
3、持久层
- Apache MyBatis 3.5.x
- Hibernate Validation 6.0.x
- Alibaba Druid 1.2.x
4、视图层
- Vue 2.6.x
- Axios 0.21.x
- Element 2.15.x
3.1.2搭建框架思路
现在站在研发层面思考,先考虑,我们的开发项目需要业务模块清晰,也就是模块的代码要分开放置,这样后期我们如果要拆项目会更容易,也就是代码结构要按照模块可拆卸可装配的思路设计,那么我们可以大致分为:系统模块*1、业务模块*N。
系统模块只有一个,里面可以包含系统级别的,也就是所有模块都离不开的,例如账号信息、用户信息、日志信息等;
思考:日志信息为什么是系统模块里的呢?因为它是必须的嘛?
疑问:我感觉并不是,从代码逻辑来说,一个网站并不是必须要日志信息,但是,从网站建设层面来说,日志是必不可少的,因为会涉及到数据问题或不良操作追踪。所以一个系统往往会有添加日志信息,即便不会对用户公开,但也会保存操作等日志信息。
那么在系统模块中也更会用到日志信息来进行日志记录,所以日志信息可以归类到系统模块。当然也可以通过技术手段拆出去,但我目前没遇到过,所以不予置评,待补充
业务模块可以有多个,里面还包含了必要或不必要的业务,例如博客信息、商品信息、订单信息等;
那么我们的项目也按照这个逻辑来搭建,业务模块可以先不用考虑,后续直接建项目引入即可,先考虑系统模块。
系统模块搭建也需要考虑几点,我们至少需要1.启动服务模块、2.核心框架模块、3.工具模块;
如果不理解,可以先看后面的文件结构图!
我们从后面往前说,3.工具模块很简单,就是我们开发中常用到的工具类,例如字符串装换工具类、spring环境管理类,又或者通用常量信息类,又或者我们系统数据表的实体类(可能有业务模块会用到该实体类)等等,大致就是自己不会产生什么业务,只是给其余模块提供的类。
2.核心框架,可以理解为处理系统模块业务的类,例如security配置类、反射类,系统数据表的关联实体、持久层、业务层等;当然这里也能再进行细分。
1.启动服务模块,主要就是我们的 controller 接口和所有的系统级别配置文件application.yml
首先我们先不考虑业务模块,我们先将基础的用户账号登录、登出逻辑
既然,我们已经列好了文档结构,那么就开始写搭建框架啦!
搭建步骤:
- 创建对应的文件目录项目,此时 pom 文件只引入子包就可以;
- 引入依赖包,主要有springboot依赖包(这里面包含spring整合的context-support、security、web、aop、druid、mybatis、validation、pagehelper、redis),按照基础需要到的添加;
- 创建基础数据库,例如user表,这里一定要整理好数据存储思路,否则后面一旦有变动要修改数据库以及持久层代码和,很麻烦的;
- 工具、配置信息、基类、常量类的配置,例如 security 、 JWT、AjaxResult ;
- 持久层、业务层、表现层代码
3.2基础框架搭建
(在这里会先说明大致思路,再举例代码)
3.2.1创建基础项目
记录一下创建项目: 创建项目记录图 ,最终的目录结构完善成下图:
3.2.2引入依赖
先从父项目开始,这里我要记录一下,springboot 的依赖引入有两个常用的方式,这两种方式是有些区别的:spring-boot-starter-parent 与 spring-boot-dependencies区别 ,starter 包是通过被继承引入的,dependencies 包是直接声明引入的,我们这里选择后者,(其实starter 包里面也是继承了 dependencies 包的,而 dependencies 内为我们统一管理依赖及版本号的)
具体的 pom 以来看代码
3.2.3数据库配置
我们当前先实现账号的基本登录、登出功能,然后后面加入附属信息角色、部门,系统菜单、权限配置等业务功能。
先实现账号登录登出,只需要用户表就可以,先考虑到用户表里面会不会与其他表有属性关联,用户表只与角色、部门有关,然后分析,用户与角色之间是多对多的(一个用户有多个角色,一个角色对应多个用户),用户与部门之间是多对一(一个用户对一个部门,一个部门有多个用户);
数据表构建时按照:
一对一:先考虑放到一张表里面,如有特殊情况,比如字段太多,可以创建外键,用父表主键id子表主键id关联;
例如:
(1).用户表(用户主键id,用户名,密码,用户住址,用户年龄)
(2).用户表(用户主键id,用户名,密码),用户详情(用户详情主键id,用户主键id,用户住址,用户年龄)
用户详情表里的用户主键id 只会对应一个 用户详情主键id ;
查询时,根据用户主键id查询用户详情;
一对多:创建外键,用父表主键id,子表主键id关联;如有特殊情况,比如不想改动一的数据表字段,也可以创建关联表,看下方多对多。
例如:
部门表(部门主键id,部门名称,部门编码)
用户表(用户主键id,部门主键id,用户名,密码)
用户表里的部门主键id 会对应多个 用户主键id ;
查询时根据部门主键id查询用户;
多对多:创建关联表,关联两个表的主键id;
例如:
用户表(用户主键id,部门主键id,用户名,密码)
角色表(角色主键id,角色名称,角色编码)
用户_角色_关联表(主键id,用户主键id,角色主键id)
用户表、角色表不添加字段,新创建一个关联表,里面保存两张表的主键id;
查询时,join 关联表一起查询,【例如查询用户及角色时,from 用户表并且 left join 用户_角色_关联表 on 用户主键id = 用户主键id left join 角色表 on 角色主键id = 角色主键id】
所以用户表里面可以提前保存一个部门id,当然也可以不添加,后续创建部门时创建对应关联表也可以,一般来说能不创建就不创建,否则数据表会太多影响使用。
数据表如下,有些字段现阶段不会使用,如登录IP,性别等:
CREATE TABLE `sys_user` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`dept_id` bigint(20) DEFAULT NULL COMMENT '部门ID',
`user_name` varchar(30) NOT NULL COMMENT '用户账号',
`nick_name` varchar(30) NOT NULL COMMENT '用户昵称',
`user_type` varchar(2) DEFAULT '00' COMMENT '用户类型(00后台用户,01前台用户)',
`email` varchar(50) DEFAULT '' COMMENT '用户邮箱',
`phonenumber` varchar(11) DEFAULT '' COMMENT '手机号码',
`sex` char(1) DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
`avatar` varchar(100) DEFAULT '' COMMENT '头像地址',
`password` varchar(100) DEFAULT '' COMMENT '密码',
`status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`login_ip` varchar(128) DEFAULT '' COMMENT '最后登录IP',
`login_date` datetime DEFAULT NULL COMMENT '最后登录时间',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=101 DEFAULT CHARSET=utf8 COMMENT='用户信息表';
3.2.4基础配置
基础配置信息可以说是核心部分,在这里将会考虑到后端返回格式、controller或实体或异常基类、常量类、security配置等信息。
我们先将关联程度低的添加进去,例如后端返回格式、controller或实体或异常基类、常量类,先将基础的加进入,同时如果里面有用到工具类啥的,也就一并引入其相关依赖,(记得做好说明为啥引入依赖,哪里用到了,小心前面写后面忘),
基础的配置好后,就配置耦合度高的security ,这个相关的地方太多了,还可能会和controller啥的相关联,就按照实际使用一步步添加吧。
3.2.5仅实现用户登录和token校验接口(未连接数据库)
1.先将 common 模块的基本信息补充完整,下面的图片记录一下补充的类,具体的看代码:
2.准备 framework 模块的内容,为什么不先准备 system 模块呢?由于 system 模块主要是系统的mapper +service 操作,主要是系统模块的业务,所以我们可以最后再慢慢加。
现在主要解决登录业务的配置!
列一下大概需要什么配置:
- 首先是 security 配置类,里面要配置黑白名单、jwt过滤器、认证失败和无权限处理器、用户验证类、security加密类等啥的
- security框架的配置类又涉及到,我们可以实现 UserDetailsService 写一个自定义用户校验接口,其中会涉及到用户实体类等bean类;UserDetailsService 接口里面会使用到 mapper 文件操作数据库,我们可以先 new User 来代替;
- jwt过滤器,处理token数据并判断是否有效
- jwt过滤器里面涉及到token就需要添加 TokenService 类,具体方法有创建、更新、获取token等;
- tokenService 类既然生成 token ,就需要进行缓存保存token以及token对应的用户信息,否则我们通过请求对象获取到 token 后无法验证用户身份,这里我们可以用 spring-redis 来实现缓存,那么就需要配置 spring-redis ,为了方便使用可以创建一个 redis 操作的工具类
- 最后我们添加 controller 类,来验证一下登录业务!
大概就是这些,感觉不多,但是配置起来可头疼了!!!
具体的包对应,以及相关报错、缺包啥的就记录一下:
1.security配置
配置类:com.qingchen.framework.config.SecurityConfig
里面主要涉及到:HttpSecurity、BCryptPasswordEncoder、AuthenticationManager、AuthenticationManagerBuilder ,具体看代码,已加好注释
2.登录用户的校验相关类
用户校验类:com.qingchen.framework.web.service.UserDetailsServiceImpl implements UserDetailsService
用户entity类:com.qingchen.common.core.domain.entity.SysUser extends BaseEntity
登录用户身份权限model类:com.qingchen.common.core.domain.model.LoginUser implements UserDetails {
注意哦,我们这里实现 UserDetails 接口时,一定要给 getPassword() 、getUsername() 返回我们定义的值,否则在 AuthenticationProvider 里面校验密码时是会获取不到的哦
用户登录model类:com.qingchen.common.core.domain.model.LoginBody
3.JWT过滤器
过滤器类:com.qingchen.framework.security.filter.JwtAuthenticationTokenFilter extends OncePerRequestFilter
这里会使用到doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain),需要导入 javax.servlet-api.jar 包
4.token处理
JWTtoken验证处理类:com.qingchen.framework.web.service.JwtTokenService
注意,这里会用到 @Value 注解,会去 yml 文件中获取配置字段,注意要添加哦
5.springredis配置
注意,这里需要到 yml 文件中配置 redis 链接信息,如果没有添加是不会报错的,但是如果代码使用了 RedisTemplate ,就会报 ConnectException 异常
springredis工具类:com.qingchen.common.core.redis.SpringRedisCache
这里可能会疑惑,为什么我们没有创建 RedisTemplate Bean 但是却能在这个类里面注入呢?因为 spring-boot-autoconfigure 包里面有个 spring.factories 文件里面会帮助我们自动创建一些 Bean ,哇塞!
redis配置类:com.qingchen.framework.config.RedisConfig
这里又会疑惑,已经有 RedisTemplate Bean 有为什么还要创建呢?原因在于redis转存获取会进行序列化,这也是为了对象能够跨平台存储查询,而实现这些就需要进行序列化;为了保证各个系统能够正确拿到数据,我们就需要统一序列化方式!
FastJson序列化类:com.qingchen.common.utils.serializer.FastJson2JsonRedisSerializer
6.接口类
登录接口类:com.qingchen.controller.system.SysLoginController
可以在里面添加两个黑白名单接口,用来测试 jwttoken 拦截是否有效
登录业务类:com.qingchen.framework.web.service.SysLoginService
注意,这里直接用authenticationManager.authenticate(*)方法来校验用户信息
有几个注意点和提醒点:
1.如果启动时报下面的错误,说明咱们依赖了数据库相关包,但是并没有配置数据库链接的数据。
所以,要么:1.注释掉相关包,例如:mybatis-spring-boot-starter、druid-spring-boot-starter;
要么:2.在启动类中排除掉数据库连接的连接配置文件;要么:3.在 yml 配置文件中添加连接数据;
方法一好说直接去 common 模块 pom 包中注释掉;
方法二已经写在启动类里面了;
方法三在后面连接数据库中会写在 yml 中;
***************************
APPLICATION FAILED TO START
***************************
Description:
Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.
Reason: Failed to determine a suitable driver class
2.redis 一定要启动好,再启动程序
3.由于我们创建的时父子项目,所以记住启动类一定要在其他所有 bean 类包的上面级别,否则会扫描不到!!!(像现在的目录结构,启动类一定要在顶级!),比如现在启动类包就是 com.qingchen.QingChenApplication ,而其他的类都在 com.qingchen.common/system/framework 包下面,这样就会被扫描到了!
由于我之前将启动类放到了 com.qingchen.admin 下面,导致 security 配置未生效,瞎找了老半天呢!!!
4.如果我们需要生成一个账号和密码,可以直接创建一个 main 主方法,用.encode("admin123")生成密码,可以见com.qingchen.BCryptPasswordEncoderUtil 类;
5.如果我们想要查看一下当前程序里面所有的 bean ,或者说想看一下某些bean有没有被创建,可以创建 CommandLineRunner 来查看,具体看:com.qingchen.config.TestRunBeanConfig
到目前为止代码可以看 Gitee 地址 注意是 project01 分支哦
下一篇,开始连接数据库,并且实现上述用户模块的基本功能接口