项目开发
1.模块功能开发
1.1 CRUD
package com.zhou.project.modules.islimp.area.controller;
import com.zhou.project.modules.islimp.area.entity.Area;
import com.zhou.project.modules.islimp.area.service.AreaService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* @author zhouhonggang
* @version 1.0.0
* @project spring-boot-project
* @datetime 2021-11-09 11:06
* @description: TODO
*/
@RestController
@RequestMapping("area")
public class AreaController {
@Autowired
private AreaService areaService;
@PostMapping
public void save(@RequestBody Area entity)
{
areaService.save(entity);
}
@PutMapping
public void update(@RequestBody Area entity)
{
areaService.updateById(entity);
}
@DeleteMapping("{id}")
public void delete(@PathVariable int id)
{
areaService.removeById(id);
}
@GetMapping("tree")
public List<Map<String, Object>> queryTree(@RequestBody Map<String, Object> params)
{
return areaService.queryTree(params);
}
@GetMapping("tree/{id}")
public List<Area> tree(@PathVariable int id)
{
return areaService.queryAreaByPid(id);
}
@GetMapping("{id}")
public Area load(@PathVariable int id)
{
return areaService.getById(id);
}
}
1.2 接口API
package com.zhou.project.modules.islimp.area.controller;
import com.zhou.project.modules.islimp.area.entity.Area;
import com.zhou.project.modules.islimp.area.service.AreaService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;
import java.util.List;
import java.util.Map;
/**
* @author zhouhonggang
* @version 1.0.0
* @project spring-boot-project
* @datetime 2021-11-09 11:06
* @description: TODO
*/
@Api(tags = "[区域接口]")
@RestController
@RequestMapping("area")
public class AreaController {
@Autowired
private AreaService areaService;
@PostMapping
@ApiOperation("添加区域数据")
@ApiImplicitParams({
@ApiImplicitParam(name = "areaCode", value = "区域编号", required = true, dataTypeClass = String.class),
@ApiImplicitParam(name = "areaName", value = "区域名称", required = true, dataTypeClass = String.class),
@ApiImplicitParam(name = "areaLevel", value = "区域级别", required = true, dataTypeClass = Integer.class),
@ApiImplicitParam(name = "parentId", value = "上级区域主键", required = true, dataTypeClass = Integer.class),
@ApiImplicitParam(name = "areaRemarks", value = "区域备注", dataTypeClass = String.class)
})
public void save(@RequestBody @ApiIgnore Area entity)
{
areaService.save(entity);
}
@PutMapping
@ApiOperation("修改区域数据")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "区域主键", required = true, dataTypeClass = Integer.class),
@ApiImplicitParam(name = "areaCode", value = "区域编号", required = true, dataTypeClass = String.class),
@ApiImplicitParam(name = "areaName", value = "区域名称", required = true, dataTypeClass = String.class),
@ApiImplicitParam(name = "areaLevel", value = "区域级别", required = true, dataTypeClass = Integer.class),
@ApiImplicitParam(name = "parentId", value = "上级区域主键", required = true, dataTypeClass = Integer.class),
@ApiImplicitParam(name = "areaRemarks", value = "区域备注", dataTypeClass = String.class)
})
public void update(@RequestBody @ApiIgnore Area entity)
{
areaService.updateById(entity);
}
@DeleteMapping("{id}")
@ApiOperation("删除区域数据")
@ApiImplicitParam(name = "id", value = "区域主键", required = true, dataTypeClass = Integer.class)
public void delete(@PathVariable int id)
{
areaService.removeById(id);
}
@GetMapping("tree")
@ApiOperation("查询区域树形结构")
@ApiImplicitParam(name = "level", value = "level=1则不查询镇", required = true, dataTypeClass = Integer.class)
public List<Map<String, Object>> queryTree(@RequestBody Map<String, Object> params)
{
return areaService.queryTree(params);
}
@GetMapping("tree/{id}")
@ApiOperation("查询区域树形 子集节点")
@ApiImplicitParam(name = "id", value = "上级区域主键", required = true, dataTypeClass = Integer.class)
public List<Area> tree(@PathVariable int id)
{
return areaService.queryAreaByPid(id);
}
@GetMapping("{id}")
@ApiOperation("查询单个区域节点")
@ApiImplicitParam(name = "id", value = "区域主键", required = true, dataTypeClass = Integer.class)
public Area load(@PathVariable int id)
{
return areaService.getById(id);
}
}
1.3 数据校验
package com.zhou.project.modules.islimp.area.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zhou.project.components.base.entity.Base;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.Range;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
/**
* @author zhouhonggang
* @version 1.0.0
* @project spring-boot-project
* @datetime 2021-11-09 11:03
* @description: TODO
*/
@Getter
@Setter
@TableName(value = "islimp_area")
public class Area extends Base {
@NotBlank(message = "区域编号不可为空")
@Length(min = 5, max = 60, message = "区域编号长度要求在{min}-{max}之间")
private String areaCode;
@NotBlank(message = "区域名称不可为空")
@Length(min = 2, max = 60, message = "区域名称长度要求在{min}-{max}之间")
private String areaName;
@NotNull(message = "区域级别不可为空")
@Range(min = 1, max = 5, message = "区域级别只能是{min}-{max}之间")
private Integer areaLevel;
@NotNull(message = "上级区域不可为空")
@Range(min = 0, max = Integer.MAX_VALUE, message = "上级区域需要在{min}-{max}之间数字")
private Integer parentId;
@Length(max = 200, message = "区域备注最多200个字符")
private String areaRemarks;
}
@PostMapping
@ApiOperation("添加区域数据")
@ApiImplicitParams({
@ApiImplicitParam(name = "areaCode", value = "区域编号", required = true, dataTypeClass = String.class),
@ApiImplicitParam(name = "areaName", value = "区域名称", required = true, dataTypeClass = String.class),
@ApiImplicitParam(name = "areaLevel", value = "区域级别", required = true, dataTypeClass = Integer.class),
@ApiImplicitParam(name = "parentId", value = "上级区域主键", required = true, dataTypeClass = Integer.class),
@ApiImplicitParam(name = "areaRemarks", value = "区域备注", dataTypeClass = String.class)
})
public void save(@RequestBody @ApiIgnore @Valid Area entity)
{
areaService.save(entity);
}
1.4 日志添加
package com.zhou.project.modules.islimp.area.controller;
import com.zhou.project.components.logger.annotation.LoggerOperation;
import com.zhou.project.components.logger.enumerate.LoggerEnumerate;
import com.zhou.project.modules.islimp.area.entity.Area;
import com.zhou.project.modules.islimp.area.service.AreaService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
/**
* @author zhouhonggang
* @version 1.0.0
* @project spring-boot-project
* @datetime 2021-11-09 11:06
* @description: TODO
*/
@Api(tags = "[区域接口]")
@RestController
@RequestMapping("area")
public class AreaController {
@Autowired
private AreaService areaService;
@PostMapping
@ApiOperation("添加区域数据")
@ApiImplicitParams({
@ApiImplicitParam(name = "areaCode", value = "区域编号", required = true, dataTypeClass = String.class),
@ApiImplicitParam(name = "areaName", value = "区域名称", required = true, dataTypeClass = String.class),
@ApiImplicitParam(name = "areaLevel", value = "区域级别", required = true, dataTypeClass = Integer.class),
@ApiImplicitParam(name = "parentId", value = "上级区域主键", required = true, dataTypeClass = Integer.class),
@ApiImplicitParam(name = "areaRemarks", value = "区域备注", dataTypeClass = String.class)
})
@LoggerOperation(module = "区域管理", message = "添加区域", type = LoggerEnumerate.INSERT)
public void save(@RequestBody @ApiIgnore @Valid Area entity)
{
areaService.save(entity);
}
@PutMapping
@ApiOperation("修改区域数据")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "区域主键", required = true, dataTypeClass = Integer.class),
@ApiImplicitParam(name = "areaCode", value = "区域编号", required = true, dataTypeClass = String.class),
@ApiImplicitParam(name = "areaName", value = "区域名称", required = true, dataTypeClass = String.class),
@ApiImplicitParam(name = "areaLevel", value = "区域级别", required = true, dataTypeClass = Integer.class),
@ApiImplicitParam(name = "parentId", value = "上级区域主键", required = true, dataTypeClass = Integer.class),
@ApiImplicitParam(name = "areaRemarks", value = "区域备注", dataTypeClass = String.class)
})
@LoggerOperation(module = "区域管理", message = "修改区域", type = LoggerEnumerate.UPDATE)
public void update(@RequestBody @ApiIgnore @Valid Area entity)
{
areaService.updateById(entity);
}
@DeleteMapping("{id}")
@ApiOperation("删除区域数据")
@ApiImplicitParam(name = "id", value = "区域主键", required = true, dataTypeClass = Integer.class)
@LoggerOperation(module = "区域管理", message = "删除区域", type = LoggerEnumerate.DELETE)
public void delete(@PathVariable int id)
{
areaService.removeById(id);
}
@GetMapping("tree")
@ApiOperation("查询区域树形结构")
@ApiImplicitParam(name = "level", value = "level=1则不查询镇", required = true, dataTypeClass = Integer.class)
@LoggerOperation(module = "区域管理", message = "查询区域树形结构", type = LoggerEnumerate.SELECT)
public List<Map<String, Object>> queryTree(@RequestBody Map<String, Object> params)
{
return areaService.queryTree(params);
}
@GetMapping("tree/{id}")
@ApiOperation("查询区域树形 子集节点")
@ApiImplicitParam(name = "id", value = "上级区域主键", required = true, dataTypeClass = Integer.class)
@LoggerOperation(module = "区域管理", message = "查询区域树形 子集节点", type = LoggerEnumerate.SELECT)
public List<Area> tree(@PathVariable int id)
{
return areaService.queryAreaByPid(id);
}
@GetMapping("{id}")
@ApiOperation("查询单个区域节点")
@ApiImplicitParam(name = "id", value = "区域主键", required = true, dataTypeClass = Integer.class)
@LoggerOperation(module = "区域管理", message = "查询单个区域节点", type = LoggerEnumerate.SELECT)
public Area load(@PathVariable int id)
{
return areaService.getById(id);
}
}
1.5 缓存处理
1.问题
1.减少应用与数据库的频繁交互
2.查询大量的数据对于数据库会有很大负担
2.方案
1.数据库读写分离
192.168.1.101 3306 master 负责写入
192.168.1.102 3306 slave 负责读取 1/2
192.168.1.103 3306 slave 负责读取 1/3
192.168.1.104 3306 slave 负责读取
2.加入缓存管理策略
application -> 缓存中间件 -> mysql
Redis
1.6权限管理
1.RBAC
RBAC: role based access of control (基于角色的访问控制)
R: role
B: based
A: access
C: control
system_user_info 用户信息表
system_role_info 角色信息表
system_user_role 用户角色中间表
system_permissions_info 权限信息表
system_role_permissions 角色权限中间表
一个用户拥有多个角色
每个角色拥有多个权限
用户 1
角色 2
权限 3
区域管理模块
分组管理
灯具管理
区域
分组
灯具
灯箱
策略
历史
… …
SpringSecurity
1.完成 区域 分组 灯具 1 2 3 4 5
2.http://127.0.0.1:9527/system-ui/index.html
向权限表中添加所有模块的权限
角色
用户
3.spring security
登陆 token
认证 授权
2.SpringSecurity
spring security默认提供了过滤器拦截客户端所有请求.
基于浏览器
1.在内存中模拟账号密码等信息
2.注入UserService查询我们自己的数据账号与密码
前后端分离开发
3.重写收集登录参数的方式 applicaiton/json UsernamePasswordToken(username, password)
4.interface userDetailsService -> loadUserByUsename(String username)
4.1: 验证账号是否存在
4.2: 验证账号是否锁定
4.3: 验证密码是否正确
5.重写登陆失败方法 unsuccessfulAuthentication
返回登陆页面 -> 提示错误信息
6.重写登陆成功方法 successfulAuthentication
生成token通过响应返回到客户端
Android, Ios, PC, 微信小程序
PC: localStorge.setItem("token", response.data.token); localStorge.removeItem("token"); sessionStorge.setItem("token", response.data.token); sessionStorge//关闭浏览器即可销毁 微信小程序: wx.syncLocalStorge('token', response.data.token); Android: Map.of("token", token);
7.鉴权过滤器
从请求头获取token, 从而验证token是否有效!
token生成策略
1.Jwt
服务器端生成token但不负责存储
当客户端请求携带token只验证时效性
优势: 跨平台, 服务器压力小
劣势: 生成后无法修改
2.Redis+UUID
中间件: Redis缓存中间件
3.Redis
1.数据类型
String
Hash
List
Set
Zset
Stream
2.持久化
RDB: Redis Database
AOF: Append Only File
1.RDB
在指定的时间间隔内将内存中的数据集快照写入磁盘(保存Snapshot快照),它恢复时是将快照文件直接读到内存里,Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
主进程: 只完成内存数据操作
子进程: 负责向磁盘写入数据
0
--------5----------------------10------------------------15--------16---------持久化
5->磁盘写入 10->磁盘写入 15-
2.AOF
以日志的形式来记录每个写操作(读操作不记录),将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
3.哨兵机制
哨兵机制的出现是为了解决主从复制的缺点。 当主节点出现故障时,由Redis Sentinel自动完成故障发现和转移,并通知应用方,实现高可用性。
4.并发策略
1.缓存雪崩
问题
由于原有缓存失效,新缓存未到期间(我们设置缓存时采用了相同的过期时间,在同一时刻出 现大面积的缓存过期),所有原本应该访问缓存的请求都去查询数据库了,而对数据库 CPU 和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
方案
1.锁 双重校验锁
//4 if(cache != null) { return this.cache; } else { //3等待 //2等待 加锁 //1加锁 释放锁 synchronized(this) { if (cache != null) { return this.cache; } else { select * from table setCache(data); } } }
2.错峰执行
2.缓存穿透
问题
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。
方案
1.缓存null
防止请求查询缓存没有, 进而查询数据库
CacheManger -> cachingNullValues
2.布隆过滤器
布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。
3.缓存预热
问题
缓存预热这个应该是一个比较常见的概念,缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
方案
1.系统启动阶段数据预热 -> 数据量少
2.直接写个缓存刷新页面,上线时手工操作下!
3.定时刷新缓存;
4.缓存更新
5.缓存降级
11.11
挑选商品 -> 确认商品 -> 点击购买 -> 选择收货地址 -> 选择支付方式 -> 支付成功
5.选举策略
1.法定数 大多数
quorum 法定数:sentinal 集群中会配置该参数,例如 5 台机器,配置的 quorum 为 3,那 么如果有 3 个节点认为 master 宕机了,sentinal 集群就会认为 master 宕机了,每个节点认为宕机被称为主观宕机 sdown,sentinal 集群认为 master 宕机被称为客观宕机 odown
majority 大多数: sendtinal 集群内大多数无需人为干预, 集群自动计算,例如 2 的majority=2,3 的 majority=2,4 的 ajority=2,5 的 majority=3
2.主客观宕机
sdown 是主观宕机,就一个哨兵如果自己觉得一个 master 宕机了,那么就是主观宕机
odown 是客观宕机,如果 quorum 数量的哨兵都觉得 master 宕机了,那么就是客观宕机