最近也是马上学完黑马的ssm和springboot的相关知识,那么也重新回顾一下我的第一个案例/项目,梳理思路以及所思所感,如若有任何问题欢迎评论区指正!
首先该项目是使用了springboot框架以及mybatis等框架的一个小案例。根本目的在于将springboot和mybatis相关知识复习及上手。
在一个springboot的项目中,会将不同功能的代码分在起码四个层中存放,这四个基础层是controller,pojo,mapper,service。在本项目中,还存在anno,aop,config,exception,filter,interceptor,utils这几个包存在。除此之外,也学习并使用了xml,yml,properties来配置相应的依赖及mapper的方法。本案例的前端来自黑马准备好的nginx-1.22.0-tlias,我也不是太懂前端,故不在此赘述。那么我将从每个包的具体作用及学到的知识这两方面开始总结。
一、controller层
1.1 EmpController
controller层下有四个方法,分别是dept,emp,login,upload。也对应着部门管理,员工管理,登录管理和更新管理。该层主要是与前端交互,获取前端接收到的数据,再调用service层,通过service层与mapper层交互从而实现操作数据库实现crud的逻辑。
dept的逻辑与emp逻辑相差不多,故在这里只讲述emp层的实现逻辑。那么我从主方法开始讲述,既然要实现增删改查,那么controller方法中肯定要有增删改查的逻辑,而且四个方法大同小异我们拿删除员工举例:
该功能的接口文档如图所示,请求路径为/emps/{ids},那么我们就要通过@DeleteMapping这个注解来定义请求路径,之后使用@PathVariable这个方法获得前端传入的Integer类型的id,再定义删除方法,调用service层的delete方法将获取到的id传进去,之后响应给前端一个json格式的数据。具体代码如下:
@DeleteMapping("/{ids}") public Result delete(@PathVariable List<Integer> ids){ *log*.info("删除所选员工:{}",ids); empService.delete(ids); return Result.*success*(); }
这里deletemapping没有写全请求路径的原因是因为我在该controller方法上使用了@RequestMapping这个注解,这个注解可以将通用的路径参数提取出来,也避免了很多代码的冗余。
由于部门员工较多所以会使用分页展示这个功能,接口文档如下:
根据接口文档,我们得知请求路径为/emps由于在前面加了注解故在此不用标注,我们直接定义一个返回值为result的方法,将页数和每页展示的员工数量定义好,
代码如下:
@GetMapping public Result page(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer pageSize, String name, Short gender, @DateTimeFormat(pattern = "yyyy-MM-dd")LocalDate begin, @DateTimeFormat(pattern = "yyyy-MM-dd")LocalDate end){ *log*.info("分页查询,参数:{},{},{},{},{},{}",page,pageSize,name,gender,begin,end); //调用service方法进行分页查询 PageBean pagebean= empService.page(page,pageSize,name,gender,begin,end); return Result.*success*(pagebean); }
在page方法中传入参数:page定义页码,pageSize定义分页展示的数量,name,gender见名知意,使用datetimeformat定义时间格式,入职日期和最后操作时间。将这些参数都传入page方法。调用service的方法控制mapper的list集合展示所获取的数据返回给前端。
需要注意的是:在修改员工数据的时候需要先获取要修改的员工的id再通过id调用update方法更新员工数据。
1.2 LoginController
该类主要是用来判断登录用户是否存在的,请求路径为/login使用@RequestBody获取前端传入的用户对象。调用service层的实现类与mapper层交互获取数据库中的用户数据,并且判断两边获得的数据是否相同,若相同直接发放jwt令牌,若不相同直接返回错误信息。具体代码如下:
`@PostMapping(“/login”)
public Result login(@RequestBody Emp emp){
log.info(“判断是否存在该用户:{}”,emp);
Emp e = empService.login(emp);
if(e != null){
Map<String,Object> cliams = new HashMap<>();
cliams.put(“name”,e.getName());
cliams.put(“id”,e.getId());
cliams.put(“username”,e.getUsername());
String jwt = JwtUtils.generateJwt(cliams);
return Result.success(jwt);
}
return Result.error(“用户名或密码错误”);
}`
使用哈希集直接消除相同的选项,只留下一个用户信息,最后使用该用户信息生成jwt令牌,最后return jwt令牌即可。
1.3 UploadController
该类主要是将前端上传的图片保存起来,可以使用本地储存方法,但是本地储存过于落后也容易造成数据丢失。所以在本项目中我采用了阿里云oss储存上传的图片。本地储存方法贴在这里供参考,并不过多赘述:
`@PostMapping(“upload”)
public Result upload(String name, Integer age, MultipartFile image) throws IOException {
log.info(“传入的姓名为{},年龄为{},图片为{}”,name,age,image);
//获取到原始的文件名
String p = image.getOriginalFilename();
// 取得.的索引然后截取后缀再将新生成的随机的uuid转成字符串拼接到后缀之前,这就是新的文件名
int a = p.lastIndexOf(“.”);
String newFileName = UUID.randomUUID().toString()+p.substring(a);
log.info(“新的文件名为{}”,newFileName);
//将文件存入本地磁盘中
image.transferTo(new File(“D:\uploadImage\”+newFileName));
return Result.success();
}`
使用阿里云oss储存文件得先去阿里云配置好endpoint,accessKeyId,accessKeySecret,bucketName这几个重要参数,还要在properties中配置。阿里云相关操作不细说。我们通过upload方法传入类型为MultipartFile的参数调用aliossutils工具类中的方法upload传入该图像生成url将url上传,并将url返回到前端。具体代码如下:
`@PostMapping(“/upload”)
public Result upload(MultipartFile image) throws IOException {
log.info(“上传的文件名为:{}”,image.getOriginalFilename());
String url = aliOSSUtils.upload(image);
log.info(“上传的文件url为:{}”,url);
return Result.success(url);
}`
二、utils层
因为上一节使用了utils这层,本层内容也不多故先讲解本层。本层只有两个类AliOSSUtils和JwtUtils。
2.1 AliOSSUtils
本工具类的作用就是实现上传图片至oss,我们会使用io流获取上传文件的输入流,new一个oss的对象,调用ossClient的方法将bucketName, fileName,和inputStream三个参数传入,所以也得引入alibaba的相关依赖:<groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version>
别忘了有可能传入相同文件名的图像造成覆盖,所以我们使用uuid的random方法生成随机数重命名该文件即可,之后将文件访问路径直接返回并且直接关闭输入流即可。具体代码如下:
`// 获取上传的文件的输入流
InputStream inputStream = file.getInputStream();
// 避免文件覆盖
String originalFilename = file.getOriginalFilename();
String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf(“.”));
//上传文件到 OSS
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
ossClient.putObject(bucketName, fileName, inputStream);
//文件访问路径
String url = endpoint.split(“//”)[0] + “//” + bucketName + “.” + endpoint.split(“//”)[1] + “/” + fileName;
// 关闭ossClient
ossClient.shutdown();
return url;// 把上传到oss的路径返回`
2.2 JwtUtils
该方法旨在生成jwt令牌和解析jwt令牌,JWT(JSON Web Token)是一种用于在网络应用间传递信息的开放标准(RFC 7519)。它以 JSON 格式存储被加密后的信息,通常用于验证和身份认证。**这是token验证的一种令牌。叫身份验证令牌。在前后端分离的架构中常用。**本质上是一个json格式的数据。将设置好的jwt令牌返回,都是jwt的相关使用方法。具体代码如下:
`*/**
* *** *生成JWT令牌
* *** @param claims JWT**第二部分负载 payload *中存储的内容
* *** **@return
** **/
*public static String generateJwt(Map<String, Object> claims){
String jwt = Jwts.builder()
.addClaims(claims)
.signWith(SignatureAlgorithm.HS256, signKey)
.setExpiration(new Date(System.currentTimeMillis() + expire))
.compact();
return jwt;
}
/*
* *** *解析JWT令牌
* *** @param jwt JWT令牌
* *** @return *JWT第二部分负载 payload *中存储的内容
* **/
*public static Claims parseJWT(String jwt){
Claims claims = Jwts.parser()
.setSigningKey(signKey)
.parseClaimsJws(jwt)
.getBody();
return claims;
}`
三、exception
该层主要是处理每个层之间未处理的异常,因为我们在各个层的异常都是直接上抛,并没有解决,我们在这里直接给出统一的解决方法——联系我,故代码如下:
@ExceptionHandler(Exception.class) public Result ex(Exception ex){ ex.printStackTrace(); return Result.*error*("对不起,操作失败,请联系我"); }
四、service
本层有两个实现类和两个接口,两个接口会定义抽象方法,之后在本层抽象方法的实现类中调用mapper层的sql语句执行应该的操作。例如我们会在抽象类中定义save新建员工方法,在实现类中实现并且调用mapper的具体方法,代码如下:
//新增员工 void save(Emp emp);
@Override public void save(Emp emp) { emp.setCreateTime(LocalDateTime.*now*()); emp.setUpdateTime(LocalDateTime.*now*()); empMapper.insert(emp); }
五、pojo
本层即定义每个具体对象应有的属性值,方便其他层调用避免重复定义。拿emp举例,代码如下:
@Data @NoArgsConstructor @AllArgsConstructor public class Emp { private Integer id; //ID private String username; //用户名 private String password; //密码 private String name; //姓名 private Short gender; //性别 , 1 男, 2 女 private String image; //图像url private Short job; //职位 , 1 班主任 , 2 讲师 , 3 学工主管 , 4 教研主管 , 5 咨询师 private LocalDate entrydate; //入职日期 private Integer deptId; //部门ID private LocalDateTime createTime; //创建时间 private LocalDateTime updateTime; //修改时间 }
六、mapper
该层就是引用sql方法,从而实现操作数据库的功能,可以直接编写sql语句也可以在resources中使用xml文件对方法进行编写,我们拿插入进行举例,我是选择直接编写sql语句,代码如下:
@Insert("insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time)"+ "values (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime})") void insert(Emp emp);
七、filter/interceptor和config
本层主要是实现jwt令牌的两种实现方式,过滤器和拦截器,两种功能相近但不一样,之后详细介绍。
八、anno/aop
这两层主要是面向切面编程从而实现在增删改的方法上添加log注解,从而实现在增删改这三个方法实现后记录日志的功能。定义一个Log空annotation,之后仅在方法上添加该注解即可应用aop方法。比如我们要记录操纵者的相关信息,我们就可以先定义该方法,并使用ProceedingJoinPoint将参数传入然后再根据相关方法把要记录的东西记录下来,代码如下:
`@Autowired
private HttpServletRequest request;
@Autowired
private OperateLogMapper operateLogMapper;
@Around(“@annotation(com.itheima.anno.Log)”)
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
//操作人id
String jwt = request.getHeader(“token”);
Claims claims = JwtUtils.parseJWT(jwt);
Integer OperatorId = (Integer) claims.get(“id”);
//操作时间
LocalDateTime now = LocalDateTime.now();
//操作类名
String ClassName = joinPoint.getTarget().getClass().getName();
//操作方法名
String mName = joinPoint.getSignature().getName();
//操作方法参数
Object[] args = joinPoint.getArgs();
String argsString = Arrays.toString(args);
//开始时间
long begin = System.currentTimeMillis();
//按原始方法运行
Object result = joinPoint.proceed();
//结束时间
long end = System.currentTimeMillis();
//操作方法返回值
String MenthodReturn = JSONObject.toJSONString(result);
//操作耗时
long time = end - begin;
//插入数据库
OperateLog operateLog = new OperateLog(null,OperatorId,now,ClassName,mName,argsString,MenthodReturn,time);
operateLogMapper.insert(operateLog);
log.info(“aop记录操作日志;{}”,operateLog);
return result;
}`
使用的很多方法也都是joinpoint的方法,故需要引入
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId>
该依赖即可。
九、总结
至此我们也完成了该项目的绝大多数的总结,具体课程可参照黑马2023/3/21的135至182集,具体代码我已上传至github
链接:H-ikaRI/tlias (github.com)欢迎浏览!!!若有什么问题也可私聊或评论区交流~
需要引入
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId>
该依赖即可。
九、总结
至此我们也完成了该项目的绝大多数的总结,具体课程可参照黑马2023/3/21的135至182集,具体代码我已上传至github,链接:H-ikaRI/tlias (github.com)欢迎浏览!!!若有什么问题也可私聊或评论区交流~