业务需求
- 在区域列表查询中,需要显示每个区域的点位数
(1)同步存储:在区域表中有点位数的字段(冗余字段6.),当点位发生变化时,同步区域表中的点位数。
优点:由于是单表查询操作,查询列表效率最高。
缺点:需要在点位增删改时修改区域表中的数据,有额外的开销,数据也可能不一致(在更新点位数据时,如果事务没有正确处理或者系统出现故障(如网络中断、数据库宕机等),可能会导致区域表中的点位数未能及时更新或更新失败,从而造成数据不一致的情况。)。
(2)关联查询:编写关联查询语句,在mapper 层封装。
优点:实时查询,数据100%正确,不需要单独维护
缺点:SQL语句较复杂,如果数据量大,性能比较低。
SELECT r.*, COUNT(n.id) AS node_count FROM tb_region r LEFT JOIN tb_node n ON r.id = n.region_id GROUP BY r.id;
左外连接(LEFT OUTER JOIN)
左外连接会返回左表(FROM子句中的第一个表)的所有记录,以及右表(JOIN子句中的第二个表)中与左表相匹配的记录。如果右表中没有与左表匹配的记录,则结果集中相应的列将为NULL。
- 在点位列表查询中,会关联显示区域、商圈等信息
关联实体:我们会采用Mybatis提供的嵌套查询功能。
MyBatis 嵌套查询就是将原来多表查询中的联合查询语句拆成单个表的查询,再使用mybatis的语法嵌套在一 起,通过定义resultMap和sql语句中的association(一对一或多对一)或collection(一对多)元素来实现嵌套查询。
点位与区域商圈是多对一的关系
货道与商品是多对一
定义一个新的resultMap标签作为selectNodeVoList的xml语句的返回结果
- 数据完整性
当我们删除区域或合作商数据时,与之关联的点位数据该如何处理?
RESTRICT(限制):在尝试删除或更新父表中的记录之前,数据库首先检查是否有相关联的子记录存在。如果有,则拒绝执行删除或更新操作,以防止意外丢失数据或破坏数据关系的完整性。这是一种保守策略,确保数据间的引用完整性。
在MYSQL图形化界面-修改表-修改表的删除规则即可
4.错误提示信息统一处理
修改完毕后,如果你尝试进行删除操作,会发现数据库的完整性约束生效了,它会阻止删除操作并给出错误提示。但是,这个错误提示信息可能对于用户来说不够友好,可能会让用户感到困惑。
SpringBoot全局异常处理器
com.dkd.framework.web.exception.GlobalExceptionHandler
5.前端:修改时需要显示创建时间,新建时不需要
使用v-if标签,修改时会回显id而新增时不会。修改时id!= null
- 修改区域表时对应的员工表的区域字段也需更改
关系到两张表的操作,使用@Transational注解开启事务
设置 rollbackFor = Exception.class 表示只要方法执行过程中抛出任何异常,都会触发事务回滚。这样可以确保数据的一致性和完整性,避免因异常导致的数据错误。
- 日期格式化
前端
后端
8.小数精度问题
商品价格在java中如果用double会有精确度降低的风险,所以后端返回的价格是以分为单位的int类型,前端展示为元为单位就需要除以100
9.将商品管理展示的商品类型id改为商品类型名称进行展示(前端)
查询商品类型列表->当商品类型id等于当前选中的id时再进行展示
10.逻辑外键
在删除商品时,需要判断此商品是否被售货机的货道关联,如果关联则无法删除(外键)
物理外键约束(在字段后面加上属性):通过在子表中添加一个外键列和约束,该列与父表的主键列相关联,由数据库维护数据的一致性和完整性
逻辑外键约束:在不使用数据库外键约束的情况下,通常在应用程序中通过代码来检查和维护数据的一致性和完整性
使用逻辑外键约束的原因(sku_id = 商品表的id):我们在新增售货机货道记录时暂不指定商品,货道表中的SKU_ID有默认值0,而这个值在商品表中并不存在,那么物理外键约束会阻止货道表的插入,因为0并不指向任何有效的商品记录
在实际业务场景中,有时需要先创建货道记录,但暂时不确定具体对应哪个商品。
为了方便操作,可以将 SKU_ID 设置为默认值 0,表示“暂未分配商品”。
删除商品方法前面加上对商品关联货道的查询,即根据商品id查询货道数量,如果大于0则抛出异常
- 批量新增(技术5.)
public List<T> importExcel(InputStream is)
1.接收一个输入流 is 作为参数。
2.尝试调用 importExcel 方法,并传入输入流和索引 0(表示首张工作表)。
12.DTO转VO可以用stream的map,最后collect收集
13.用于接收前端传过来的各种参数(工单类型)
运营和运营工单共享一套后端接口,通过特定的查询条件区分工单类型,并在返回结果中包含工单类型的详细信息
所有的类都继承BaseEntity
接收的参数为map集合,key的名称为isRepair
14.用户登录流程(后端)(技术点9.)
1.验证码校验
·检查系统是否开启验证码功能。
·若开启,则从Redis缓存redisCache中获取对应uuid(唯一标识)的验证码。并从缓存中删除
·如果验证码已过期或不存在,记录登录失败信息并抛出异常。
·比较用户输入的验证码与实际验证码,不一致时记录失败信息并抛出异常。
- 登录前置校验
·检查用户名和密码是否为空,若空则记录登录信息并抛出异常。
·验证密码长度是否在规定范围内,否则记录信息并抛出异常。
·检查用户名长度是否符合要求,不符合则记录信息并抛出异常。
·校验客户端IP地址是否在黑名单中,若在黑名单,则记录登录失败信息 并抛出异常。
- ss认证管理器用户校验
·使用 UsernamePasswordAuthenticationToken 对象进行用户认证,放入AuthenticationContextHolder中(底层是ThreadLocal,来为每个线程提供独立的身份验证信息存储空间。)
若认证失败,记录日志并抛出异常;
认证成功后记录成功登录信息,并依据认证信息记录用户登录详情;
4.登陆成功,记录日志。
5.更新登录用户信息
6.生成token,并将登录用户信息缓存在redis中。
7.返回token
- 获取用户角色和权限
查询该用户权限(菜单)集合用set集合,一个用户可能有多个角色,多个角色的权限可能有重复的
16.数据权限
我们有一个系统登录日志,里面记录了所有用户的登录信息。
但是,并不是所有人都应该看到所有的日志数据。所以,我们需要根据用户的角色来控制他们能查看的数据范围。
17.跨域
在前端开发中,跨域是一个常见的问题,特别是在使用Vue框架进行开发时。跨域是指在浏览器中发送的AJAX请求的目标地址与当前页面的地址不在同一个域下,这会导致浏览器的同源策略产生限制,从而阻止了跨域请求的发送。然而,我们可以通过代理服务器来解决这个问题。
代理服务器是位于客户端和目标服务器之间的一台服务器,它接收客户端发送的请求,并将请求转发给目标服务器。通过在代理服务器上进行请求转发,可以绕过浏览器的同源策略限制,从而实现跨域请求。
技术点
- MyBatis 嵌套查询
MyBatis 嵌套查询就是将原来多表查询中的联合查询语句拆成单个表的查询,再使用mybatis的语法嵌套在一 起,通过定义resultMap和sql语句中的association(一对一或多对一)或collection(一对多)元素来实现嵌套查询。
主sql语句为前者
- SpringBoot全局异常处理器
SQLIntegrityConstraintViolationException是Java中的一个异常类,这个类通常用于表示SQL数据库操作中的完整性约束违反异常
例如:外键约束、唯一约束等。当数据库操作违反了这些约束时,就会抛出这个异常。
这个错误是由于外键约束导致的。它表明在删除或更新父表的行时,存在外键约束,子表中的相关行会受到影响。
是因为在删除tb_region表中的行时,tb_node表中的region_id外键约束会阻止操作。
如果你在使用Spring框架进行数据库操作,可能会先遇到DataIntegrityViolationException,它是对SQLIntegrityConstraintViolationException的一个更高层次的抽象,旨在提供一种更加面向应用的错误表示。
而SQLIntegrityConstraintViolationException是更底层的异常,直接来源于数据库驱动,包含更多底层数据库相关的细节。
在实际开发中,推荐捕获并处理DataIntegrityViolationException,因为它更符合Spring应用的异常处理模式,同时也可以通过其内部的cause(原因)属性来获取具体的SQLIntegrityConstraintViolationException,进而获取详细的错误信息。
3.MtBatis注解
1.@Param: 当方法中有多个参数时,使用 @Param 可以为每个参数指定一个名字。这在 SQL 映射文件中引用参数时非常有用,特别是当需要传递多个参数给 SQL 语句时。
4.x-file-storage
一行代码将文件存储到本地、阿里云 OSS、华为云 OBS等
5.EasyExcel
基于JAVA的EXCEL处理工具
easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出
- Redis计数器
自动生成工单编号-使用redis的计数器
redisTemplate.opsForValue().set(key, 1, Duration.ofDays(1));
使用opsForValue()方法获取操作字符串值的对象。
调用set方法存储值1。
使用Duration.ofDays(1)设置键的有效期为一天。
dateStr+StrUtil.padPre(redisTemplate.opsForValue().increment(key).toString(),4,'0');
从Redis中获取指定键的值,并对其执行自增操作。
将自增后的值转换为字符串。
在字符串左侧填充‘0’,确保总长度为4位。
将填充后的字符串与日期字符串(dateStr)拼接后返回。
7.Knife4j
如果不习惯使用swagger可以使用前端UI的增强解决方案knife4j,对比swagger相比有以下优势,友好界面,离线文档,接口排序,安全控制,在线调试,文档清晰,注解增强,容易上手。
TaskDetailsController添加swagger注解
@Api: 用于类级别,描述API的标签和描述。
@ApiOperation: 用于方法级别,描述一个HTTP操作。
@ApiParam: 用于参数级别,描述请求参数。
注意:若依框架的AjaxResult由于继承自HashMap导致与Swagger和knife4j不兼容的问题,选择替换返回值类型为R<>以解决Swagger解析问题,减少整体改动量。
8.Velocity模版引擎
实体类支持Lombok(@Data),Controller类支持Swagger(@Apioperation)
(导入坐标,修改模板代码)
Velocity是一个基于Java的模板引擎,可以通过特定的语法获取在java对象的数据 , 填充到模板中,从而实现界面和java代码的分离 !
Velocity中的变量有两类
在模板中定义变量: #set开头,比如 #set($name = "velocity")
获取变量的的值: $name 或者 ${name}
9.RBAC权限控制
1.SpringSecurity配置:SecurityConfig类上加上@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
开启方法级别的权限控制
方法的controller层加上@PreAuthorize("@ss.hasPermi('manage:taskType:list')")
@PreAuthorize:Spring Security注解,用于在执行方法前检查用户权限。
@ss.hasPermi:自定义权限校验方法,验证用户是否具有指定权限。
ss:Spring容器管理的一个bean
@PreAuthorize 是 Spring Security 框架中提供的一个安全注解,用于实现基于注解的访问控制。它允许开发者在方法级别上声明特定的安全约束,以确保只有满足指定条件的用户才能调用该方法
- 当 @PreAuthorize 注解被应用于某个方法时,Spring Security 在该方法执行前会先对当前认证的用户进行权限检查。如果检查通过,方法调用得以继续;否则,框架会抛出相应的权限异常(如 AccessDeniedException),阻止方法执行。
2.如果有些接口是不需要验证权限可以公开访问的,这个时候就需要我们给接口放行。
使用注解方式,只需要在Controller的类或方法上加入@Anonymous该注解即可
10.异步任务管理器
主要用于处理一些不需要即时返回结果的后台任务,从而提高应用程序的整体性能
// 多线程执行任务me()创建单例对象(饿汉式)
AsyncManager.me().execute(AsyncFactory.createTimerTask());
若依异步任务管理器是一个单例对象使用了线程池+异步工厂(产生任务用)
1、 AsyncManager.me()获取AsyncManager对象
2、调用execute方法,执行TimerTask任务(记录登录日志),它实现了runnable接口,由线程Thread去执行
3、execute方法内部调用ScheduledExecutorService异步操作任务调度线程池的schedule方法用于延迟10毫秒执行一个任务
11.操作日志
在需要被记录日志的controller方法上添加@Log注解
若依操作日志使用了自定义注解+AOP切面+异步任务管理器
通过实现AOP切面编程,对目标方法进行拦截(标注Log注解的方法),实现了操作日志的自动记录
异步任务管理器来将任务(记录操作日志到数据库)交给线程池来完成
- 定时任务
13.数据权限
在系统中,权限的分配和控制主要依赖于角色。每个角色可以被赋予不同的菜单权限和数据权限,用户则通过他们的角色来继承这些权限,进而决定他们能访问哪些系统资源。
目前,系统支持以下五种数据权限类型:
全部数据权限:无限制访问所有数据,相当于拥有最高权限的通行证。
自定数据权限:用户可以根据自己的需求设定访问特定数据的规则。
部门数据权限:只能访问自己所在部门的数据,限制在本部门范围内。
部门及以下数据权限:可以访问自己部门及下属部门的数据,适用于管理层级。
仅本人数据权限:只能访问和操作自己的数据,保障个人隐私和数据隔离。
在需要数据权限控制方法上添加@DataScope注解,其中d和u用来表示表的别名
在mybatis查询底部标签添加数据范围过滤(其作用就是相当于在一个 select 语句后面拼接一个 and 条件语句,来实现查询限制)
若依数据权限底层使用了自定义注解+AOP切面+SQL拼接
通过实现AOP编程,对目标方法进行拦截(标注DataScope 注解的方法),实现了构建数据范围SQL过滤条件
仅实体继承`BaseEntity`才会进行处理,`SQL`语句会存放到`BaseEntity`对象中的`params`属性中,然后在`xml`中通过`${params.dataScope}`获取拼接后的语句。