创新骨科临床应用系统项目详解(Power By 小赵先生)
项目概览
核心领域:
- 医生: 管理医生的信息,包括医生的级别、专长等。
- 药品: 管理药品的信息,与药企(药品公司)有关联。
- 销售: 记录与销售有关的信息。
- 材料/物料: 管理与医疗相关的材料或物料。
- 医疗政策: 与医保、优惠政策或其他医疗相关的政策信息。
- 治疗类型: 管理不同的治疗方法或方式。
基础设施:
- 账户管理: 对用户账户进行管理。
- 权限管理: 用于控制不同用户或角色对系统功能的访问权限。
- 文件上传: 提供文件上传功能。
- 系统配置: 包括Swagger配置、Web安全配置等,用于支持API文档生成、安全验证等功能。
工具与辅助模块:
- JWT: 项目使用JWT (JSON Web Token) 进行身份验证和授权。
- Swagger: 用于API文档的生成和测试。
- JSON处理: 对JSON数据进行特定的处理。
业务流程概述
- 用户访问: 用户通过前端界面访问系统,首先进行登录验证,然后根据角色和权限,访问相应的功能模块。
- 数据管理: 各个模块(如医生、药品、销售等)都有对应的增删改查操作,数据通过Mapper层与数据库交互,数据结构由实体类、模型类和参数类定义。
- 业务逻辑: Service层包含了业务逻辑,例如计算、数据转换、数据验证等。
- API交互: Controller层提供了外部API接口,供前端或其他系统调用。
从以下几个方面进行分析:
- API接口定义: 从
FileUploadController.java分析文件上传的API接口定义和参数。 - 业务逻辑: 从
FileUploadService.java分析文件上传的具体逻辑,例如文件存储的位置、文件名生成策略、错误处理等。 - 其他相关配置: 如有必要,我还会查找其他可能与文件上传相关的配置信息。
首先,查看FileUploadController.java的内容。
从FileUploadController.java的内容中,我们可以得到以下关于文件上传功能的信息:
API接口定义:
- 请求路径:
/api/base/upload,这是文件上传的基础路径。 - 请求方法: 使用
@PostMapping注解,表示这是一个POST请求。 - 参数: 文件上传的API接口接受一个名为
file的参数,其类型为MultipartFile。这表示用户可以通过此参数上传文件。
- 请求路径:
控制器的功能:
- 控制器使用了
FileUploadService服务来处理文件上传,具体的上传逻辑在这个服务类中。 - 上传成功或失败的消息通过
Msg对象返回。这可能是一个自定义的消息或响应类,用于向前端返回处理结果。
- 控制器使用了
其他注解与说明:
- 使用了
@CrossOrigin注解,允许跨域请求。 @RolesAllowed({"1","2"})注解表示只有角色1和角色2的用户可以访问此上传接口。@Api(tags = "文件上传控制器")是Swagger的注解,用于API文档的描述。
- 使用了
接下来,查看FileUploadService.java的内容,以分析文件上传的具体业务逻辑。
从FileUploadService.java的内容中,我们可以得到以下关于文件上传功能的信息:
业务逻辑:
文件名生成:
- 使用
UUID.randomUUID()为上传的文件生成一个唯一的文件名,保证每次上传的文件都有唯一的名字,避免重名文件被覆盖。
- 使用
文件存储位置:
- 文件被保存在路径为
C:/Users/dell/Pictures/medical的位置。如果此目录不存在,会创建这个目录。 - 保存的完整路径为:
C:/Users/dell/Pictures/medical/ + UUID + 原始文件名。
- 文件被保存在路径为
文件保存操作:
- 使用
InputStream读取上传的文件内容。 - 使用
OutputStream将文件内容写入指定路径。 - 读取和写入过程中使用了一个大小为1024字节的缓冲数组,以提高文件读写效率。
- 使用
错误处理:
- 使用try-catch-finally结构处理可能的
IOException。 - 如果出现异常,则返回一个表示“上传失败”的消息。
- 在finally块中,确保输入和输出流都被正确关闭。
- 使用try-catch-finally结构处理可能的
返回结果:
- 如果文件上传成功,会返回一个包含文件访问URL的消息。URL的格式为
http://localhost:8080/image/ + 文件名。 - 如果出现错误,返回一个表示“上传失败”的消息。
- 如果文件上传成功,会返回一个包含文件访问URL的消息。URL的格式为
从这段代码中可以看出,文件上传的业务逻辑相对简单,主要涉及文件名的生成、文件的保存、以及错误的处理。
我们可以按照代码的执行流程进行逐步调试分析。
从FileUploadController.java和FileUploadService.java两个文件出发,我们可以模拟一个文件上传请求的处理流程。
1. 请求开始于 FileUploadController
当用户通过前端或其他客户端发起一个文件上传请求时,请求首先会被映射到FileUploadController中的fileUpload方法。
1 |
|
- 这里,我们使用
@PostMapping注解定义了一个POST类型的API接口。 - 接口接受一个类型为
MultipartFile的参数file,这个参数包含了用户上传的文件内容。 - 接着,控制器调用了
fileUploadService.upload(file)方法,并将返回的消息对象Msg返回给调用者。
2. 请求进入 FileUploadService 的处理
在FileUploadService的upload方法中,文件的实际处理逻辑开始执行。
1 | public Msg upload(MultipartFile file){ |
- 首先,我们从
MultipartFile对象中获取文件的输入流inputStream。 - 接下来,为了确保文件名的唯一性,我们使用
UUID.randomUUID()生成一个随机UUID,并与原始文件名组合成新的文件名。
1 | fileName = UUID.randomUUID() + file.getOriginalFilename(); |
- 接着,我们定义了文件保存的目标路径
path。
1 | String path = "C:/Users/dell/Pictures/medical"; |
- 在写入文件之前,我们检查目标路径是否存在。如果不存在,则创建对应的目录。
1 | File tmpFile = new File(path); |
- 使用
FileOutputStream创建文件的输出流os,并指定文件的完整保存路径。 - 接下来,使用一个循环从
inputStream中读取文件内容,并写入到目标文件中。
1 | while((len = inputStream.read(bs)) != -1) { |
- 如果文件处理过程中发生任何异常,我们捕获该异常并返回一个“上传失败”的消息。
1 | } catch (IOException e) { |
- 最后,在finally块中确保输入和输出流都被正确关闭。
1 | finally { |
- 如果文件成功保存,我们构造一个包含文件访问URL的消息并返回。这个URL可以用于在前端显示或下载已上传的文件。
1 | String url = "http://localhost:8080/image/" + fileName; |
接下来我们将深入分析账户管理模块。
与账户管理相关的文件包括:
- Account.java
- AccountEntity.java
- AccountInfoModel.java
- AccountMapper.java
- AccountMapper.xml
- AccountModel.java
我们将按照以下步骤进行分析:
- 数据结构: 分析
AccountEntity.java、Account.java和AccountModel.java,了解账户的基本数据结构和属性。 - 数据访问层: 查看
AccountMapper.java和AccountMapper.xml,了解与数据库交互的SQL语句和方法定义。 - 业务逻辑: 如果存在AccountService.java或类似的文件,我们会分析业务逻辑层。
首先,让我们查看AccountEntity.java文件的内容,以了解账户的基本数据结构和属性。
从AccountEntity.java文件内容来看,这个实体类非常简单,它继承自Account类。这意味着AccountEntity使用了Account类中定义的所有属性和方法,并可能根据需要添加了一些额外的属性或方法,但在当前文件中没有显示。
为了进一步了解账户的属性和数据结构,我们需要查看Account.java文件的内容。让我们接下来分析这个文件。
从Account.java文件内容中,我们可以得到以下信息:
账户属性:
- id: 账户的唯一标识,类型为
Long。 - uname: 用户名,类型为
String。 - pwd: 密码,类型为
String。 - phoneNumber: 手机号码,类型为
String。 - utype: 用户类型,类型为
String。注释说明了不同的用户类型:ROLE_1代表管理员,ROLE_2代表医生,ROLE_3代表患者。 - updatetime: 账户的最后更新时间,类型为
Date。 - createtime: 账户的创建时间,类型为
Date。 - realname: 真实姓名,类型为
String。
继承关系:
Account类继承自SuperDomain类。这意味着Account类可能还有其他从SuperDomain继承来的属性和方法。具体的内容需要查看SuperDomain类来确定。
方法:
对于每个属性,Account类都提供了对应的getter和setter方法。
注释:
文件的末尾有一个注释:“在domain包下创建用户实体类Account继承SuperDomain”。这提供了一些关于这个类的背景信息。
接下来,分析AccountMapper.java和AccountMapper.xml,以了解与数据库交互的SQL语句和方法定义。
我们按顺序来。首先,我们将查看AccountMapper.java文件的内容,这个文件应该定义了与数据库交互的接口方法。
让我们看看AccountMapper.java的内容。
从AccountMapper.java的内容中,我们可以看到以下与数据库交互的方法定义:
用户登录:
- 方法名:
securityLogin - 参数:
uname(用户名) - 返回类型:
AccountModel - 功能: 根据用户名查询用户信息,可能用于用户登录验证。
- 方法名:
手机号验证:
- 方法名:
checkPhone - 参数:
phone(手机号码) - 返回类型:
int - 功能: 检查给定的手机号是否已存在。
- 方法名:
重置密码:
- 方法名:
resetPwd - 参数:
id(用户ID) 和pwd(新密码) - 返回类型:
int - 功能: 为指定用户ID重置密码。
- 方法名:
注册账号:
- 方法名:
regist - 参数:
AccountEntity entity(用户实体) - 返回类型:
int - 功能: 注册一个新的账户。
- 方法名:
更新账户信息:
- 方法名:
updateAccount - 参数:
AccountEntity entity(用户实体) - 返回类型:
int - 功能: 更新指定账户的信息。
- 方法名:
该接口定义了基本的账户管理功能,如登录、注册、重置密码和更新账户信息。这些方法与数据库进行交互,具体的SQL语句定义应在AccountMapper.xml文件中。
接下来,我们将查看AccountMapper.xml文件的内容,以了解具体的SQL语句和数据库操作细节。
从AccountMapper.xml文件中,我们可以看到与数据库交互的SQL语句定义:
用户登录:
1
2
3<select id="securityLogin" resultType="com.qst.medical.model.AccountModel">
select * from account where uname=#{uname} or phonenumber=#{uname}
</select>- 根据用户名或手机号查询用户信息。
- 返回值映射到
AccountModel对象。
手机号验证:
1
2
3<select id="checkPhone" resultType="int">
select count(id) from `doctor` where phone=#{phoneNumber} limit 1
</select>- 检查给定的手机号是否已存在于
doctor表中。 - 返回匹配的记录数(0或1)。
- 检查给定的手机号是否已存在于
注册账号:
1
2
3
4<insert id="regist" useGeneratedKeys="true" keyProperty="id">
insert into account(uname,realname,pwd,phonenumber,utype,updatetime,createtime)
values(#{uname},#{realname},#{pwd},#{phoneNumber},#{utype},#{updatetime},#{createtime})
</insert>- 向
account表中插入新用户的记录。 - 使用
useGeneratedKeys属性获取自动生成的主键值。
- 向
更新账户信息:
1
2
3<update id="updateAccount">
update account set <include refid="updateValue"></include> where id=#{id}
</update>- 根据用户ID更新账户信息。
- SQL片段
updateValue中包含了更新的字段和值。
删除账户信息:
1
2
3<delete id="deleteAccountById">
delete from account where id=#{id}
</delete>- 根据用户ID从
account表中删除用户记录。
- 根据用户ID从
此外,AccountMapper.xml中还包含了一些SQL片段,如updateValue,用于构造动态的SQL语句。
我们可以像之前那样,按照调试的方式逐步分析账户管理模块的代码。
1. 用户登录
假设一个用户尝试登录,那么以下流程将会被触发:
在AccountMapper.java中:
1 | AccountModel securityLogin(String uname); |
当这个方法被调用时,它对应于AccountMapper.xml中的以下查询:
1 | <select id="securityLogin" resultType="com.qst.medical.model.AccountModel"> |
流程:
- 用户输入的
uname将被作为参数传递。 - SQL查询将在
account表中搜索与用户名或手机号匹配的记录。 - 如果找到匹配的记录,该记录的所有字段值将被映射到一个
AccountModel对象,并返回。 - 如果没有找到匹配的记录,将返回null。
2. 手机号验证
当一个新用户尝试注册时,系统可能会先验证手机号是否已经被注册。
在AccountMapper.java中:
1 | int checkPhone(String phone); |
对应的SQL查询为:
1 | <select id="checkPhone" resultType="int"> |
流程:
- 用户输入的手机号将被作为参数传递。
- SQL查询会在
doctor表中查找匹配的手机号。 - 返回匹配的记录数。如果手机号已存在,返回值将大于0;否则返回0。
3. 注册账号
新用户完成注册信息填写后,信息将被保存到数据库中。
在AccountMapper.java中:
1 | int regist(AccountEntity entity); |
对应的SQL插入语句为:
1 | <insert id="regist" useGeneratedKeys="true" keyProperty="id"> |
流程:
- 用户填写的信息被封装成一个
AccountEntity对象,并作为参数传递。 - SQL插入语句将新用户的信息保存到
account表中。 - 如果插入成功,返回新记录的ID。
4. 更新账户信息
用户可能需要更新其账户信息,如更改密码或手机号。
在AccountMapper.java中:
1 | int updateAccount(AccountEntity entity); |
对应的SQL更新语句为:
1 | <update id="updateAccount"> |
流程:
- 用户更改的信息被封装成一个
AccountEntity对象,并作为参数传递。 - SQL更新语句将对应的字段在
account表中进行更新。 - 如果更新成功,返回受影响的记录数。
这就是账户管理模块的核心功能流程。每一步中的逻辑都与数据库进行了交互,确保数据的一致性和完整性。
权限管理模块
权限管理模块通常涉及到对用户、角色、和权限的管理,以及这些实体之间的关联关系。
从您上传的文件列表中,与权限管理相关的文件可能包括:
- Permission.java
- PermissionController.java
- PermissionMapper.java
- PermissionMapper.xml
- PermissionModel.java
- PermissionService.java
我们将按照以下步骤进行分析:
- 数据结构: 分析
Permission.java和PermissionModel.java,了解权限的基本数据结构和属性。 - 数据访问层: 查看
PermissionMapper.java和PermissionMapper.xml,了解与数据库交互的SQL语句和方法定义。 - 业务逻辑: 分析
PermissionService.java中的方法,了解权限管理的业务逻辑。 - API接口: 查看
PermissionController.java,了解提供给前端或其他客户端的API接口。
首先,让我们查看Permission.java文件的内容,以了解权限的基本数据结构和属性。
从Permission.java文件内容中,我们可以得到以下关于权限(或称为菜单管理)的信息:
菜单属性:
- id: 菜单的唯一标识,类型为
Integer。 - pid: 菜单的父ID,类型为
Integer。它表示菜单之间的层级关系。 - path: 菜单的路径,类型为
String。可能用于URL路径或路由。 - name: 菜单名称,类型为
String。 - component: 菜单组件,类型为
String。可能是前端组件的名称或路径。 - level: 菜单的级别,类型为
Integer。表示菜单的深度或层级。 - children: 子菜单列表,类型为
List<Permission>。这表示菜单有一个子菜单列表,用于构建多级菜单结构。
方法:
对于每个属性,Permission类都提供了对应的getter和setter方法。
从这个类的结构和命名来看,它似乎不仅仅是关于权限,更像是一个菜单管理系统的实现,其中每个菜单项可以有子菜单,形成一个多级菜单结构。此外,每个菜单项可能与一个前端组件相关联,这在SPA(单页应用)中很常见,其中每个菜单可能对应一个页面或视图组件。
接下来,我将分析PermissionMapper.java和PermissionMapper.xml,以了解与数据库交互的SQL语句和方法定义。首先,我们看看PermissionMapper.java的内容。
从PermissionMapper.java文件内容中,我们可以看到与数据库交互的方法定义如下:
1. 获取权限
- 方法名:
getPermission - 参数:
roleName(角色名称) - 返回类型:
List<PermissionModel> - 功能: 根据角色名称获取与之关联的权限列表。
这个方法很简单,它的主要目的是根据特定的角色名称查询相关的权限列表,这对于权限控制和角色管理是非常有用的。
接下来,我们将查看PermissionMapper.xml文件的内容,以了解具体的SQL语句和数据库操作细节。
从PermissionMapper.xml文件内容中,我们可以看到与数据库交互的SQL语句定义如下:
查询所有菜单的映射:
- 映射ID:
selectMap - 类型:
PermissionModel - 字段映射:
- id -> id
- pid -> pid
- name -> name
- path -> path
- component -> component
- level -> level
- title (与
MetaModel关联) -> title
获取权限:
- 方法ID:
getPermission - 返回映射:
selectMap - SQL语句:
1
2
3
4SELECT p.id,p.pid,p.`name`,p.path,p.component,p.`level`,p.title
FROM role_permission rp
LEFT JOIN permission p ON rp.per_id=p.id
where rp.roleName=#{roleName}
这个SQL语句的功能是:根据提供的角色名称,从role_permission表中查询与该角色关联的所有权限,然后与permission表进行左连接,以获取详细的权限信息。
简而言之,这个查询将返回与指定角色关联的所有权限。
这就是权限管理模块的核心功能。从这个模块中,我们可以看到它主要关注于根据角色获取权限。
我们将按照调试模式深入分析权限管理模块的代码。我们首先从API调用开始,然后逐步深入到业务逻辑和数据库交互。
1. API调用
当前端或其他客户端尝试获取某个角色的权限时,它可能会调用一个API。这个API的具体实现应该在PermissionController.java中。
在PermissionController.java中:
假设存在一个方法,类似于:
1 |
|
这个方法会接受一个角色名称作为参数,并调用PermissionService的getPermission方法来获取与该角色关联的权限列表。
2. 业务逻辑
在PermissionService.java中:
假设存在以下方法:
1 | public List<PermissionModel> getPermission(String roleName) { |
这个方法简单地调用了PermissionMapper的getPermission方法,传递了角色名称,并返回查询到的权限列表。
3. 数据访问层
在PermissionMapper.java中:
如我们之前所看到的,有以下方法定义:
1 | List<PermissionModel> getPermission(String roleName); |
这个方法与PermissionMapper.xml中的SQL查询相对应。
在PermissionMapper.xml中:
对应的SQL查询为:
1 | <select id="getPermission" resultMap="selectMap"> |
流程:
- 用户输入的
roleName将被作为参数传递。 - SQL查询会从
role_permission表中查找与该角色名称匹配的所有权限记录。 - 使用左连接与
permission表进行关联,以获取详细的权限信息。 - 结果将被映射到
PermissionModel对象列表,并返回。
从上述的调试流程中,我们可以看到权限管理模块是如何从API调用开始,经过业务逻辑处理,最后与数据库交互来获取数据的。这是一个典型的三层架构模式,包括表示层(API)、业务逻辑层和数据访问层。
系统配置模块
系统配置模块通常涉及到对整个系统的各种配置参数的管理。这些参数可能包括但不限于应用设置、系统常量、运行时配置等。
从您上传的文件列表中,与系统配置相关的可能文件包括:
- application.properties
让我们从这个文件开始,因为它可能包含了系统的主要配置信息。
首先,我们将查看application.properties文件的内容,这个文件通常包含了应用的基本配置,例如数据库连接信息、服务器端口、日志设置等。
编码方式(如ISO-8859-1)
从application.properties文件中,我们可以看到以下的配置信息:
时区配置:
1
spring.jackson.time-zone=GMT+8
数据源配置:
- 数据源类型:
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource - 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver - 数据库URL:
spring.datasource.url=jdbc:mysql://localhost:3306/medical?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC - 数据库用户名:
spring.datasource.username=why - 数据库密码:
spring.datasource.password=123456
- 数据源类型:
MyBatis配置:
- 映射文件位置:
mybatis.mapper-locations=classpath:mapper/*.xml - 下划线到驼峰命名的映射:
mybatis.configuration.map-underscore-to-camel-case=true
- 映射文件位置:
PageHelper分页插件配置:
- 数据库方言:
pagehelper.helper-dialect=mysql - 分页参数合理化:
pagehelper.reasonable=true - 分页大小为零:
pagehelper.page-size-zero=true
- 数据库方言:
文件传输配置:
- 最大文件大小:
spring.servlet.multipart.max-file-size=10MB - 默认属性包含:
spring.jackson.default-property-inclusion=non_null
- 最大文件大小:
这是系统配置模块中的基本配置信息。这些配置涵盖了数据库连接、MyBatis设置、分页插件和文件上传设置等方面。
当然,根据实际的需求,可能还有其他的配置参数,但上面列出的是最常见和基础的配置。
Swagger和Web安全配置是系统配置模块的两个重要部分。让我们一一进行分析:
1. Swagger配置
Swagger是一个API文档生成工具,它可以自动地为RESTful APIs生成可视化的API文档。这些文档允许开发人员和其他相关者直观地了解API的结构和功能,还可以直接通过文档页面测试API调用。
在SwaggerConfig.java中:
这个类通常定义了Swagger的基本配置,例如API的标题、描述、版本、联系信息等。此外,它还可以定义哪些API端点应该被包括在文档中,以及哪些应该被排除。
我们将查看SwaggerConfig.java文件的内容,以了解具体的Swagger配置。
2. Web安全配置
这部分涉及到如何保护Web应用免受未授权访问。通常,这涉及到定义哪些URL路径应该被保护、哪些路径应该公开,以及如何验证用户身份等。
在WebSecurityConfig.java中:
这个类通常继承自WebSecurityConfigurerAdapter,并重写其中的方法来自定义Web安全配置。这包括定义认证和授权策略、密码加密方式、登录和注销的URL路径等。
我们将查看WebSecurityConfig.java文件的内容,以了解具体的Web安全配置。
首先,我们从SwaggerConfig.java开始。
从SwaggerConfig.java文件中,我们可以看到以下关于Swagger的配置信息:
Swagger基本配置:
配置标识:
使用@Configuration和@EnableSwagger2注解,表示这是一个Spring配置类,并启用Swagger2的功能。Docket Bean:
Docket是Swagger的主要配置类。它定义了如何描述API。1
2
3
4
5
6
7
8
9
10
public Docket webApiConfig(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("Mdeical-API")
.apiInfo(webApiInfo())
.select()
.paths(Predicates.not(PathSelectors.regex("/admin/.*")))
.paths(Predicates.not(PathSelectors.regex("/error/.*")))
.build();
}- groupName: 定义API文档的组名。
- apiInfo: 提供API的基本信息,如标题、描述、版本和联系信息。
- select: 定义要包括或排除的API路径。在此配置中,所有以
/admin/和/error/开头的路径都被排除。
API信息:
1
2
3
4
5
6
7
8public ApiInfo webApiInfo(){
return new ApiInfoBuilder()
.title("创新骨科临床应用系统-API文档")
.description("本文档描述了创新骨科临床应用系统的接口定义")
.version("1.0")
.contact(new Contact("QST", "http://www.itshixun.com/", "邮箱地址@xx.xx"))
.build();
}这部分定义了API文档的基本信息,包括标题、描述、版本和联系信息。
接下来,我们查看WebSecurityConfig.java文件来了解Web安全配置。
从WebSecurityConfig.java文件中,我们可以看到以下关于Web安全的配置信息:
基本配置:
- 配置标识:
使用@Configuration、@EnableWebSecurity和@EnableGlobalMethodSecurity注解,表示这是一个Spring配置类,并启用Web安全和方法安全的功能。
认证配置:
依赖注入:
UserDetasServiceImpl: 这是一个自定义的登录业务类,它负责从数据源检索用户信息。MyAuthenticationFailHandler: 登录失败处理器,当用户尝试登录并失败时调用。JwtFilter: JSON Web Token过滤器,用于验证JWT令牌的合法性。
密码加密器:
使用BCryptPasswordEncoder作为密码加密方式。
安全策略配置:
- 重写
configure方法:
这个方法定义了哪些URL路径应该被保护、哪些路径应该公开,以及如何验证用户身份。- JWT过滤器: 在
UsernamePasswordAuthenticationFilter之前添加jwtFilter。 - 表单登录:
- 登录URL路径:
/api/login - 登录成功和失败的处理器
- 登录URL路径:
- 异常处理: 定义了身份验证失败和访问拒绝的处理器。
- 授权请求:
- 定义了哪些URL路径是公开的,例如
/api/login,/swagger-ui.html等。 - 任何其他请求都需要经过身份验证。
- 定义了哪些URL路径是公开的,例如
- 会话管理: 定义了会话策略为无状态(不创建和使用HTTP会话)。
- 跨域配置: 允许所有来源、方法和头部进行跨域请求。
- JWT过滤器: 在
跨域配置:
configurationSource方法:
这个方法解决了前后端使用security时的跨域问题。- 允许所有来源、方法和头部进行跨域请求。
- 设置跨域请求的最大有效时间为3600秒。
总的来说,WebSecurityConfig配置类定义了如何保护应用程序的哪些部分、如何进行身份验证和授权、以及如何处理登录失败等安全相关事件。这个类为应用程序提供了一套完整的Web安全策略。
我们将按照调试模式详细分析Web安全配置和Swagger配置。首先从Web安全配置开始。
1. Web安全配置 (在WebSecurityConfig.java)
a. 初始化
当应用启动时,Spring Security将自动检测WebSecurityConfig类,因为它标注了@EnableWebSecurity。这意味着应用的安全配置将基于这个类。
b. 依赖注入
UserDetasServiceImpl: 用户详情服务,用于根据用户名加载用户信息。MyAuthenticationFailHandler: 自定义的认证失败处理器,当用户提供错误的认证信息时会被调用。JwtFilter: JWT过滤器,用于验证每个请求的JWT令牌。
c. 用户认证配置
在configure(AuthenticationManagerBuilder auth)方法中:
- 使用
UserDetasServiceImpl来加载用户详情。 - 设置密码加密器为
BCryptPasswordEncoder。
d. HTTP安全配置
在configure(HttpSecurity http)方法中:
JWT过滤器:
- 使用
addFilterBefore方法将JwtFilter添加到安全过滤器链中。
- 使用
表单登录:
- 设置登录处理URL为
/api/login。 - 定义登录成功和失败的处理器。
- 设置登录处理URL为
异常处理:
- 当用户未认证或认证失败时,定义处理的入口点和访问拒绝处理器。
授权请求配置:
- 公开
/api/login、Swagger文档和相关资源。 - 任何其他请求都需要身份验证。
- 公开
会话管理:
- 设置为无状态,不创建或使用HTTP会话。
跨域配置:
- 允许所有来源、方法和头部进行跨域请求。
2. Swagger配置 (在SwaggerConfig.java)
a. 初始化
应用启动时,Spring将自动检测SwaggerConfig类,因为它标注了@Configuration。
b. Swagger的Docket Bean
在webApiConfig方法中:
- 设置文档类型为
SWAGGER_2。 - 定义API的分组名。
- 设置API的基本信息,例如标题、描述和版本。
- 使用
select方法开始定义哪些接口应该被展示。 - 排除所有以
/admin/和/error/开头的路径。
至此,通过调试模式,我们已经深入了解了Web安全配置和Swagger配置的详细过程。
我们将开始分析工具和辅助模块。
1. JWT (JSON Web Token)
JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表示方法。在许多现代Web应用程序中,尤其是那些使用前后端分离架构的应用程序中,JWT被广泛用作身份验证和授权的方法。
a. 生成和验证JWT
在JwtUtils.java中,我们可能会找到与生成、验证和解析JWT相关的方法。
b. JWT的使用
在JwtFilter.java中,我们可能会看到JWT的实际应用,如如何从请求中获取令牌、如何验证令牌的有效性以及如何从令牌中提取用户信息。
我们先从JwtUtils.java开始。
2. Swagger
我们已经在之前的部分讨论了Swagger的配置,但我们可以再次回顾并详细说明Swagger如何生成API文档以及如何使用Swagger UI来测试API。
3. JSON处理
在JacksonConfig.java中,我们可能会找到与JSON序列化和反序列化相关的配置。这些配置可能涉及到如何格式化日期、如何处理空值以及其他与JSON处理相关的设置。
现在,我们将开始从JwtUtils.java文件分析JWT的具体实现。
我们从JwtUtils.java文件中可以看到以下关于JWT的主要功能:
1. 常量定义:
EXPIRE: 定义了JWT的过期时间,设置为6小时。APP_SECRET: 定义了用于签名的密钥。
2. 生成Token (getJwtToken方法):
- 使用
Jwts.builder()来开始构建一个JWT。 - 设置头信息,包括类型 (
JWT) 和签名算法 (HS256)。 - 设置载荷,包括:
- 主题 (
medical-user) - 发行时间
- 过期时间
- 自定义载荷(用户ID、用户名、角色)
- 主题 (
- 使用
signWith方法和APP_SECRET来签名JWT。 - 使用
compact方法将JWT构建为一个字符串。
3. 验证Token:
checkToken(String jwtToken)方法: 验证给定的JWT是否有效。checkToken(HttpServletRequest request)方法: 从HTTP请求中获取JWT并验证其有效性。
4. 获取Claims (getClaims方法):
获取JWT的载荷部分。
5. 根据Token获取会员ID (getMemberIdByJwtToken方法):
从JWT中提取会员ID。
这个类为应用提供了创建、验证和解析JWT的基本功能。
接下来,我们已经讨论了Swagger的配置,所以我们将跳到JSON处理部分,即查看JacksonConfig.java文件。
从JacksonConfig.java文件中,我们可以看到以下关于JSON处理的配置信息:
Jackson配置:
配置标识:
使用@Configuration注解,表示这是一个Spring配置类。ObjectMapper Bean:
ObjectMapper是Jackson库中的核心类,用于处理Java对象和JSON数据之间的转换。在
jacksonObjectMapper方法中,有以下配置:@Bean,@Primary, 和@ConditionalOnMissingBean: 这些注解确保这个特定的ObjectMapper实例是应用中的主要实例,并且只在没有其他ObjectMapperBean存在时创建。builder.createXmlMapper(false): 这确保ObjectMapper不会处理XML,只处理JSON。objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL): 这表示当Java对象序列化为JSON时,所有为NULL的属性都将被忽略,不会出现在生成的JSON中。
注释:
该文件还包含一些注释,说明了其他可能的JsonInclude设置,这些设置决定了哪些属性应该被序列化。Include.ALWAYS: 默认设置,所有属性都会被序列化。Include.NON_DEFAULT: 只有属性不为默认值时才序列化。Include.NON_EMPTY: 属性为空("")或为NULL时不序列化。Include.NON_NULL: 属性为NULL时不序列化。
总的来说,JacksonConfig类为应用提供了JSON序列化和反序列化的配置,确保了JSON输出与预期的格式和内容相匹配。
我们将按照调试模式详细分析JacksonConfig类。
JacksonConfig.java Debug:
a. 初始化
当应用启动时,Spring Boot将自动检测JacksonConfig类,因为它标注了@Configuration。这意味着Spring Boot将基于这个类来进行JSON处理的配置。
b. ObjectMapper Bean
当Spring容器初始化并开始创建Beans时,jacksonObjectMapper方法会被调用,因为它被标注为@Bean。
检查条件:
- 使用
@ConditionalOnMissingBean(ObjectMapper.class)注解确保这个特定的ObjectMapper实例只在没有其他ObjectMapperBean存在时创建。如果应用中已经定义了另一个ObjectMapperBean,则此方法不会执行。
- 使用
创建ObjectMapper实例:
- 调用
Jackson2ObjectMapperBuilder的builder.createXmlMapper(false)方法来创建一个新的ObjectMapper实例。这确保ObjectMapper不会处理XML,只处理JSON。
- 调用
配置ObjectMapper:
- 使用
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL)配置ObjectMapper。这意味着当Java对象序列化为JSON时,所有为NULL的属性都将被忽略,不会出现在生成的JSON中。
- 使用
返回ObjectMapper实例:
- 这个新创建和配置的
ObjectMapper实例将被返回,并由Spring容器管理。其他的Spring Beans可以注入并使用这个ObjectMapper实例来处理JSON序列化和反序列化。
- 这个新创建和配置的
至此,我们已经完成了对JacksonConfig类的调试分析。通过这个类,应用确保了JSON的序列化行为与预期相匹配。
接下来根据业务流程概述,我们将对整个项目进行详细讲解。我们会分模块进行解释,并结合相关代码部分进行分析。
1. 用户访问
a. 登录验证
- 用户首次访问系统时,需要登录。登录请求会发送到
/api/login。 UserDetasServiceImpl用于加载用户详细信息。它从数据库中查询用户信息,并与用户输入的凭据进行验证。- 使用
JwtUtils生成JWT令牌,将令牌发送回给用户。 - 在后续的请求中,用户需要在HTTP头中携带这个JWT令牌来验证身份。
JwtFilter检查请求头中的JWT令牌。如果令牌有效,用户可以继续访问;否则,请求会被拒绝。
b. 角色和权限
- 用户登录后,系统会根据用户的角色和权限决定他们可以访问哪些功能模块。
- 权限信息可能存储在数据库中,并通过
PermissionService和PermissionMapper进行管理。 WebSecurityConfig定义了各个URL路径的安全策略,确定哪些路径需要特定权限才能访问。
2. 数据管理
对于各个模块(如医生、药品、销售等):
a. 数据结构
- 实体类 (
Entity): 直接与数据库表相对应的类,通常包含与表字段相对应的属性。 - 模型类 (
Model): 用于前端和后端之间的数据交互,可能包含一些额外的业务字段或逻辑。 - 参数类 (
Param): 用于接收前端传递的参数。
b. 数据交互
- Mapper层: 使用MyBatis实现,包括Java接口和对应的XML文件。这一层负责与数据库进行直接交互,执行SQL语句。
3. 业务逻辑
- Service层: 包含业务逻辑,如数据验证、数据转换和计算等。
- Service层调用Mapper层来进行数据操作,并在必要时进行业务逻辑处理。
4. API交互
- Controller层: 提供外部API接口。它接收来自前端的请求,调用Service层处理业务逻辑,然后返回响应。
- 使用Swagger,可以为这些API生成文档,方便前端开发人员或其他系统调用者查看和测试。
这就是项目的整体结构和业务流程。每个模块都遵循这一流程,从前端请求开始,经过Controller、Service、Mapper,最终与数据库交互,然后将结果返回给用户。
最后,我们将按照调试模式对整个项目进行详细分析,从用户访问开始,逐步深入每个模块,直到与数据库的交互。
1. 用户访问
当用户首次访问应用时,他们通常会被重定向到登录页面。
a. 登录验证
- 用户输入用户名和密码后,前端将信息发送到后端的
/api/login接口。 - 后端会调用
UserDetasServiceImpl来根据用户名加载用户的详细信息。 - 用户输入的密码会与数据库中的密码进行比较。如果匹配,验证成功。
JwtUtils会生成一个JWT令牌,并将其发送回给用户。
b. JWT验证
- 用户的每次请求都会携带这个JWT令牌。
JwtFilter会检查这个令牌,验证其是否有效。如果令牌无效或过期,请求会被拒绝。- 如果令牌有效,
JwtFilter会从中提取用户的详细信息,并将其传递给后续的处理流程。
c. 权限验证
- 在
WebSecurityConfig中,为每个API路径定义了安全策略。 - 根据用户的角色和权限,系统决定他们是否可以访问特定的API或页面。
2. 数据管理
对于每个模块(如医生、药品、销售等),处理流程大致相同:
a. API调用
- 用户在前端发起操作(如添加、修改或删除记录)。
- 前端将数据或请求发送到后端的相应API接口。
b. Controller层
- 后端的Controller捕获这个请求。
- Controller可能会进行一些初步的验证或数据处理。
- 接下来,Controller将请求传递给相应的Service层。
c. Service层
- Service层处理业务逻辑,如数据验证、数据转换和计算等。
- 如果需要与数据库交互,Service层会调用Mapper层。
d. Mapper层
- Mapper层使用MyBatis与数据库进行交互。
- 对于读操作,数据从数据库加载并返回给Service层。
- 对于写操作(如添加、修改或删除),Mapper层将数据更改写入数据库。
e. 返回结果
- 数据或处理结果从Service层返回给Controller层。
- Controller将结果封装为响应并发送回前端。
3. 工具和辅助功能
项目中有一些工具和辅助功能,如:
- JWT: 用于生成和验证令牌,确保用户的请求是经过身份验证的。
- Swagger: 提供API文档,帮助开发者了解和测试API接口。
- JSON处理: 在
JacksonConfig中定义,确保JSON序列化和反序列化与预期相匹配。
这是项目的整体调试分析。每个模块和功能都遵循类似的流程。
