mengxuegu-member 作为所有工程的父工程,用于管理项目的所有依赖。
文件位于:会员管理系统/03-配套资料/pom文件/member-pom.xml
4.0.0
com.cc
cc-member
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
2.3.0.RELEASE
3.3.2
1.1.21
1.2.8
2.6
3.2.2
2.6
UTF-8
1.8
com.baomidou
mybatis-plus-boot-starter
${mybatis-plus.version}
mysql
mysql-connector-java
org.projectlombok
lombok
com.alibaba
druid-spring-boot-starter
${druid.version}
org.springframework.security
spring-security-crypto
org.springframework.boot
spring-boot-configuration-processor
true
com.alibaba
fastjson
${fastjson.version}
commons-lang
commons-lang
${commons-lang.version}
commons-collections
commons-collections
${commons-collections.version}
commons-io
commons-io
${commons-io.version}
用于管理通用的工具类
1.将 logback.xml 日志配置文件添加到 resources 目录下
${CONSOLE_LOG_PATTERN}
文件位于:会员管理系统/03-配套资料/logback.xml
Lombok 介绍
官方网址: https://www.projectlombok.org/features/all
Lombok 工具提供一系列的注解,使用这些注解可以不用定义getter、setter、equals、构造方法等,可以消除java代码的臃肿,它会在编译时在字节码文件自动生成这些通用的方法,简化开发 人员的工作
Lombok 使用
1.我们在父模块中已经添加了相关的依赖,无需再次添加
org.projectlombok
lombok
2.IDEA 安装 lombok 插件
作用: 使用IDEA开发时,使用 Lombok 注解生成方法不报错
ResultEnum 枚举类是为了搭配 Result 规范响应的结果。
在 mengxuegu-member-util 模块创建 com.mengxuegu.member.base.ResultEnum 响应结果枚举
package com.cc.member.base;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ResultEnum {
// 成功
SUCCESS(2000, "成功"),
// 错误
ERROR(999, "错误");
private Integer code;
public String desc;
}
文件位于:会员管理系统/03-配套资料/工具类/ResultEnum.java
1.说明:为了规范响应的结果,创建一个Result 类来统一响应JSON格式:
code 操作代码、flag 是否成功、message 提示信息、 data 自定义数据。
2. 在 mengxuegu-member-util 创建com.mengxuegu.member.base.Result用于封装接口统一响应结果
package com.cc.member.base;
import com.alibaba.fastjson.JSON;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
/**
* 用于封装接口统一响应结果
*/
@Data
public class Result implements Serializable {
private static final Logger logger = LoggerFactory.getLogger(Result.class);
private static final long serialVersionUID = 1L;
/**
* 响应业务状态码
*/
private Integer code;
/**
* 是否正常
*/
private Boolean flag;
/**
* 响应信息
*/
private String message;
/**
* 响应中的数据
*/
private Object data;
public Result(Integer code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
this.flag = code == ResultEnum.SUCCESS.getCode() ? true: false;
}
public static Result ok() {
return new Result(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getDesc(), null);
}
public static Result ok(Object data) {
return new Result(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getDesc(), data);
}
public static Result ok(String message, Object data) {
return new Result(ResultEnum.SUCCESS.getCode(), message, data);
}
public static Result error(String message) {
logger.debug("返回错误:code={}, message={}", ResultEnum.ERROR.getCode(), message);
return new Result(ResultEnum.ERROR.getCode(), message, null);
}
public static Result build(int code, String message) {
logger.debug("返回结果:code={}, message={}", code, message);
return new Result(code, message, null);
}
public static Result build(ResultEnum resultEnum) {
logger.debug("返回结果:code={}, message={}", resultEnum.getCode(), resultEnum.getDesc());
return new Result(resultEnum.getCode(), resultEnum.getDesc(), null);
}
public String toString() {
return JSON.toJSONString(this);
}
}
(与第二章工具模块同一级)
编写会员管理系统业务逻辑并向外提供 RESTful 风格接口给前端调用
创建mengxuegu-member-api 接口模块
mengxuegu-member-api 的 pom.xml 中添加 第二章的工具模块 和 web启动器依赖。
cc-member
com.cc
1.0-SNAPSHOT
4.0.0
cc-member-api
cc-member-util
com.cc
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-web
src/main/java
**/*.xml
src/main/resources
org.springframework.boot
spring-boot-maven-plugin
com.cc.member.MemberApplication
位于:会员管理系统/03-配套资料/pom文件/api-pom.xml
在 mengxuegu-member-api 模块的src/main/java下创建 com.mengxuegu.member.MemberApplication
package com.cc.member;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MemberApplication {
public static void main(String[] args) {
SpringApplication.run(MemberApplication.class,args);
}
}
接口模块下的resources下创建
application.yml
server:
port: 6666
# 数据源配置
spring:
datasource:
username: root
password: root
url: jdbc:mysql://127.0.0.1:3306/mxg_member?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
#mysql8版本以上驱动包指定新的驱动类
driver-class-name: com.mysql.cj.jdbc.Driver
# 数据源其他配置, 在 DruidConfig配置类中手动绑定
initialSize: 8
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
1.创建mxg_member数据库
2.导入数据库脚本:文件位于 会员管理系统/03-配套资料/mxg_member.sql
MemberController.java
package com.cc.member.controller;
import com.cc.member.base.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/member")
public class MemberController {
Logger logger= LoggerFactory.getLogger(getClass());
@PostMapping("/list/search/{page}/{size}")
public Result search(@PathVariable("page") long page,@PathVariable("size") long size){
logger.info("分类查询列表参数,page={},size={}",page,size);
return Result.ok();
}
}
@PathVariable注解的作用是从URL路径中获取参数并将其绑定到控制器方法的参数上。它可以用来捕获URL中的占位符并将其传递给控制器方法。
启动项目,使用 postman 发送 POST 请求,访问 localhost:6666/member/list/search/1/20
官网地址:Redirect
api模块中创建config包,config/MybatisPlusConfig
添加 Mybatis-Plus 配置类开启事务管理、Mapper接口扫描、 分页功能。 config/MybatisPlusConfig.java 代码:
package com.cc.member.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement // 开启事务管理
@MapperScan("com.cc.member.mapper") // 扫描mapper接口
@Configuration
public class MybatisPlusConfig {
/**
* 分页插件
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
return paginationInterceptor;
}
}
创建 com.mengxuegu.member.entity 和 com.mengxuegu.member.mapper 包
我们将 xxxMapper.xml 会放到 src/main/java 目录下,当文件编译时,默认情况下不会将 Mapper.xml文件编译到 classes 中,需要指定 **/*.xml,编译打包时,将xml一起打包。
在 mengxuegu-member-api/pom.xml 添加如下 resources 标签: (前期已经添加)
cc-member
com.cc
1.0-SNAPSHOT
4.0.0
cc-member-api
cc-member-util
com.cc
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-web
src/main/java
**/*.xml
src/main/resources
org.springframework.boot
spring-boot-maven-plugin
com.cc.member.MemberApplication
添加后记得pom.xml 文件中任意地方右击,Maven > reimport 才会生效
需求:通过会员姓名、卡号、支付类型、会员生日 条件查询列表数据,并实现分页功能。
package com.cc.member.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
/**
* 会员信息表对应实体类
*
*/
@Accessors(chain = true)
@Data
//当数据库名与实体类名不一致或不符合驼峰命名时,需要在此注解指定表名(不加这个注解默认将实体类的小写形式在db中寻找)
@TableName("tb_member")
public class Member implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 会员卡号
*/
private String cardNum;
/**
* 会员名字
*/
private String name;
/**
* 生日
*/
private Date birthday;
/**
* 手机号
*/
private String phone;
/**
* 可用积分
*/
private Integer integral;
/**
* 可用金额
*/
private Double money;
/**
* 支付类型('1'现金, '2'微信, '3'支付宝, '4'银行卡)
*/
private String payType;
/**
* 会员地址
*/
private String address;
}
使用到了Lombok
REQ:作为 request 简写,主要作用是把将查询条件请求参数封装为一个对象。比如:会员姓名、卡号、支付类型、会员生日 作为条件,查询出对应分类数据。
package com.cc.member.req;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 接收会员查询条件
*/
@Data
public class MemberREQ implements Serializable {
/**
* 会员卡号
*/
private String cardNum;
/**
* 会员名字
*/
private String name;
/**
* 生日
*/
private Date birthday;
/**
* 支付类型('1'现金, '2'微信, '3'支付宝, '4'银行卡)
*/
private String payType;
}
注:mapper对表的数据操作;
1.创建接口 com.mengxuegu.member.mapper.MemberMapper 继承 BaseMapper
接口MemberMapper的页面代码:
package com.cc.member.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cc.member.entity.Member;
/**
* 会员信息表的Mapper接口
*/
public interface MemberMapper extends BaseMapper {
}
2.java目录下创建映射文件 com/mengxuegu/member/mapper/xml/MemberMapper.xml
MemberMapper.xml代码
创建接口 com.mengxuegu.member.service.IMemberService 继承 IService
接口 实现Iservice
接口,提供了常用更复杂的对T数据表的操作,比如:支持Lambda表达式,批量 删除、自动新增或更新操作等方法
1.定义一个通过分页条件查询方法search
IMemberService接口代码:
package com.cc.member.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.cc.member.base.Result;
import com.cc.member.entity.Member;
import com.cc.member.req.MemberREQ;
public interface IMemberService extends IService {
/**
* 分布条件查询
* @param req 查询条件
* @return
*/
Result search(long page, long size, MemberREQ req);
}
2.创建一个实体类 com.memgxuegu.member.service.impl.MemberServiceImpl 继承 ServiceImpl
ServiceImpl
,T>是对Iservice 接口中方法的实现 第一个泛型M指定继承了BaseMapper接口的子接口
第二个泛型T指定实体类
注意:类上不要少@Service(不添加会报错),baseMapper引用的就是MemberMapper实例
MemberServiceImpl代码
使用到了mybatis: CRUD 接口 | MyBatis-PlusMyBatis-Plus 官方文档https://www.baomidou.com/pages/49cc81/#list
package com.cc.member.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cc.member.base.Result;
import com.cc.member.entity.Member;
import com.cc.member.mapper.MemberMapper;
import com.cc.member.req.MemberREQ;
import com.cc.member.service.IMemberService;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
@Service
public class MemberServiceImpl extends ServiceImpl implements IMemberService {
@Override
public Result search(long page, long size, MemberREQ req) {
// 封装查询条件
QueryWrapper query=new QueryWrapper();
if(req!=null){
if(StringUtils.isNotBlank(req.getName())){
query.like("name",req.getName());
}
if(StringUtils.isNotBlank(req.getCardNum())){
query.like("card_num",req.getCardNum());
}
if(req.getBirthday()!=null){
query.eq("birthday",req.getBirthday());
}
if(StringUtils.isNotBlank(req.getPayType())){
query.eq("pay_type",req.getPayType());
}
}
IPage p=new Page<>(page,size);
IPage data=baseMapper.selectPage(p,query);
return Result.ok(data);
}
}
创建控制层类 com.mengxuegu.member.controller.MemberController
MemberController.java代码:
package com.cc.member.controller;
import com.cc.member.base.Result;
import com.cc.member.req.MemberREQ;
import com.cc.member.service.impl.MemberServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/member")
public class MemberController {
Logger logger= LoggerFactory.getLogger(getClass());
@Autowired
private MemberServiceImpl memberService;
@PostMapping("/list/search/{page}/{size}")
public Result search(@PathVariable("page") long page, @PathVariable("size") long size,@RequestBody(required = false) MemberREQ req){
logger.info("分类查询列表参数,page={},size={}",page,size);
return memberService.search(page,size,req);
}
}
发送 POST 请求,访问 localhost:6666/member/list/search/1/20
出现问题:
项目启动报错:The bean ‘xxxServiceImpl’ could not be injected as a ‘cn.xxxx.service.xxxServiceImpl’ because it is a JDK dynamic proxy that implements:
解释:大概意思是默认采用jdk动态代理因为动态代理需要类实现接口,通过接口进行代理,但是我们通过 @Autowired 注解进行注入bean的时候采用了它的实现类而没有采用它的接口
解决方法:在启动类事务注解上添加proxyTargetClass=true,强迫事务使用CGLib代理方式。@EnableTransactionManagement(proxyTargetClass=true)
主要是做什莫:前端要求返回 rows, 而默认是在 records 中,下面自定义一个 Page 类继承 Mybatis-Plus 的提供的Page类,将数据返回到rows里面,不在乎这些接口的不需要去看。
1.在 mengxuegu-member-util 模块中创建 com.mengxuegu.member.base.Page
Page.java代码
package com.cc.member.base;
import lombok.Data;
import java.util.List;
/**
* 因为前端分页数据是 rows ,不是recodrs,在这里转换下
* @param
*/
@Data
public class Page extends com.baomidou.mybatisplus.extension.plugins.pagination.Page {
/**
* 因为前端分页数据是 rows ,不是recodrs
* @return
*/
public List getRows() {
// 调用父类的
return super.getRecords();
}
public List getRecords() {
return null;
}
public Page(long current, long size) {
super(current, size);
}
}
2.将 MemberServiceImpl 类导入的
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;替换import com.baomidou.mybatisplus.core.metadata.IPage;
3.重启项目,重新发送 POST 请求 localhost:6666/member/list/search/1/20
新增与删除会员只要对 tb_member 单表操作,并且我们可以直接使用 mybatis-plus 提供的 IMemberService 方法进行操作即可。
ctrl+鼠标点击 IService可以看到分装好的方法
添加控制层方法
在 com.mengxuegu.member.controller.MemberController 类中添加 add 和 delete 方法:
// 添加会员
@PostMapping // /member
public Result add(@RequestBody Member member){
boolean b=memberService.save(member);
if(b) {
return Result.ok();
}
return Result.error("新增会员信息失败");
}
// 删除会员
@DeleteMapping("/{id}")
public Result delete(@PathVariable("id") int id){
boolean b= memberService.removeById(id);
if(b){
return Result.ok();
}
return Result.error("删除会员信息失败");
}
测试:
1.新增会员,发送 POST 请求 localhost:6666/member
2.删除会员,发送 DELETE 请求 localhost:6666/member/2
添加控制层方法
在 com.mengxuegu.member.controller.MemberController 类中添加 get 和 update 方法:
// 通过id查询详情
@GetMapping("/{id}")
public Result getById( @PathVariable("id") int id){
Member mem= memberService.getById(id);
if(mem!=null){
return Result.ok(mem);
}
return Result.error("查询失败");
}
@PutMapping("/{id}")
public Result update(@PathVariable("id") int id,@RequestBody Member m){
if(m.getId()==null){
m.setId(id);
}
boolean b=memberService.updateById(m);
if(b){
return Result.ok();
}
return Result.error("修改失败");
}
测试:
1.查询会员详情,发送 GET 请求 localhost:6666/member/2
2.修改会员,发送 PUT 请求 localhost:6666/member/2
另一种修改会员的方法:(相当于自己service封装了方法,上一个方法相当于直接调用人家封装好的方法)
在com.mengxuegu.member.service.IMemberService添加 update 接口方法
Result update(int id,Member member);
在 com.mengxuegu.member.service.impl.MemberServiceImpl 实现 update 方法
package com.cc.member.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
//import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cc.member.base.Page;
import com.cc.member.base.Result;
import com.cc.member.entity.Member;
import com.cc.member.mapper.MemberMapper;
import com.cc.member.req.MemberREQ;
import com.cc.member.service.IMemberService;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
@Service
public class MemberServiceImpl extends ServiceImpl implements IMemberService {
@Override
public Result search(long page, long size, MemberREQ req) {
// 封装查询条件
QueryWrapper query=new QueryWrapper();
if(req!=null){
if(StringUtils.isNotBlank(req.getName())){
query.like("name",req.getName());
}
if(StringUtils.isNotBlank(req.getCardNum())){
query.like("card_num",req.getCardNum());
}
if(req.getBirthday()!=null){
query.eq("birthday",req.getBirthday());
}
if(StringUtils.isNotBlank(req.getPayType())){
query.eq("pay_type",req.getPayType());
}
}
IPage p=new Page<>(page,size);
IPage data=baseMapper.selectPage(p,query);
return Result.ok(data);
}
// 修改会员
@Override
public Result update(int id, Member member) {
if(member.getId()==null){
member.setId(id);
}
int b=baseMapper.updateById(member);
if(b<1){
return Result.error("修改失败");
}
return Result.ok();
}
}
memberController.java中运行
@PutMapping("/{id}")
public Result update(@PathVariable("id") int id,@RequestBody Member m){
return memberService.update(id,m);
}
选中父模块,new-module
文件位于:会员管理系统/03-配套资料/pom文件/generator-pom.xml
cc-member
com.cc
1.0-SNAPSHOT
4.0.0
cc-member-generator
com.baomidou
mybatis-plus-generator
3.3.1
org.freemarker
freemarker
代码生成器模块中创建类 com.mengxuegu.generator.CodeGenerator
代码:
package com.cc.generator;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import org.apache.commons.lang.StringUtils;
import java.util.Scanner;
public class CodeGenerator {
// 生成的代码放到哪个工程中
private static String PROJECT_NAME = "cc-member-api";
// 数据库名称
private static String DATABASE_NAME = "mxg-member";
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/"+ DATABASE_NAME +"?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useTimezone=true");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
mpg.setDataSource(dsc);
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir") + "/";
gc.setOutputDir(projectPath + PROJECT_NAME +"/src/main/java");
gc.setIdType(IdType.AUTO); // 分布式id
gc.setAuthor("cc-www.cc.com");
gc.setFileOverride(true); //覆盖现有的
gc.setOpen(false); //是否生成后打开
gc.setDateType(DateType.ONLY_DATE);
// gc.setSwagger2(true); //实体属性 Swagger2 注解
mpg.setGlobalConfig(gc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setParent("com.cc.member"); //父包名
mpg.setPackageInfo(pc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true); //使用lombok
strategy.setEntitySerialVersionUID(true);// 实体类的实现接口Serializable
strategy.setRestControllerStyle(true); // @RestController
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix("tb_"); // 去掉表前缀
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
/**
*
* 读取控制台内容
*
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
}
代码要进行一定的修改
1. 右键执行 main 方法,控制台输入表名回车,会自动生成对应项目目录中
代码:
package com.cc.member.req;
import lombok.Data;
import java.io.Serializable;
@Data
public class SupplierREQ implements Serializable {
/**
* 供应商名称
*/
private String name;
/**
* 联系人
*/
private String linkman;
/**
* 联系电话
*/
private String mobile;
}
1.service编写方法
代码:(定义分页条件查询方法)
package com.cc.member.service;
import com.cc.member.base.Result;
import com.cc.member.entity.Supplier;
import com.baomidou.mybatisplus.extension.service.IService;
import com.cc.member.req.SupplierREQ;
/**
*
* 供应商信息表 服务类
*
*
* @author cc-www.cc.com
* @since 2023-05-18
*/
public interface ISupplierService extends IService {
// 分页
public Result search(int page, int size, SupplierREQ req);
}
2.serviceImpl实现创建的方法
代码:
注意:Page导包要导入自定义的 import com.mengxuegu.member.base.Page;
package com.cc.member.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.cc.member.base.Page;
import com.cc.member.base.Result;
import com.cc.member.entity.Supplier;
import com.cc.member.mapper.SupplierMapper;
import com.cc.member.req.SupplierREQ;
import com.cc.member.service.ISupplierService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
/**
*
* 供应商信息表 服务实现类
*
*
* @author cc-www.cc.com
* @since 2023-05-18
*/
@Service
public class SupplierServiceImpl extends ServiceImpl implements ISupplierService {
@Override
public Result search(int page, int size, SupplierREQ req) {
QueryWrapper query=new QueryWrapper();
if(req!=null){
if(StringUtils.isNotBlank(req.getName())){
query.like("name",req.getName());
}
if(StringUtils.isNotBlank(req.getLinkman())) { query.like("linkman", req.getLinkman());
}
if(StringUtils.isNotBlank(req.getMobile())) { query.like("mobile", req.getMobile());
}
}
// 分装分页
IPage p=new Page<>(page,size);
IPagedata= baseMapper.selectPage(p,query);
return Result.ok(data);
}
}
代码:
package com.cc.member.controller;
import com.cc.member.base.Result;
import com.cc.member.req.SupplierREQ;
import com.cc.member.service.impl.SupplierServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
*
* 供应商信息表 前端控制器
*
*
* @author cc-www.cc.com
* @since 2023-05-18
*/
@RestController
@RequestMapping("/supplier")
public class SupplierController {
Logger logger= LoggerFactory.getLogger(getClass());
@Autowired
private SupplierServiceImpl supplierService;
// 分页
@PostMapping("/list/search/{page}/{size}")
public Result search(@PathVariable("page") int page,@PathVariable("size") int size,@RequestBody SupplierREQ req){
logger.info("分页数据:{},{}",page,size);
return supplierService.search(page,size,req);
}
}
分页条件查询供应商列表,发送 POST 请求 localhost:6666/supplier/list/search/1/20
供应商只要对 tb_supplier 单表操作,并且我们可以直接使用 mybatis-plus 提供的 ISupplierService 方法进行操作即可。
1.添加控制层方法
部分代码:
// 新增
@PostMapping
public Result add(@RequestBody Supplier supplier){
boolean b=supplierService.save(supplier);
if(b){
return Result.ok();
}
return Result.error("新增失败");
}
2.测试
通过供应商id删除供应商数据,在删除前判断该供应商是否已经被 tb_goods 商品引用了,如果 被引用则不允许删除。
通过供应商id查询商品表 tb_goods 是否存在数据,存在则供应商被引用
1.在商品的业务层定义一个 方法
// 通过供应商的id查询商品列表
List getBySupplier(int supplierId);
2.实现商品的业务层
代码
@Override
public List getBySupplier(int supplierId) {
QueryWrapper query=new QueryWrapper();
query.eq("supplier_id", supplierId);
return baseMapper.selectList(query);
}
使用 supplier_id的原因是:数据可tb_goods中的字段是supplier_id,要相对应
代码:
// 删除供应商(需要关联表,所以重新编写业务层的方法)
Result deleteSupplier(int id);
2.实现方法
代码:
// 删除供应商
@Autowired
private IGoodsService iGoodsService;
@Override
public Result deleteSupplier(int id) {
// 1. 通过供应商id查询是否被商品引用,
List goodsListBySupplier= iGoodsService.getBySupplier(id);
// 2. 如果被商品引用,则不让删除供应商
if(CollectionUtils.isNotEmpty(goodsListBySupplier)){
return Result.error("供应商有关联的商品,不允许被删除");
}
// 3. 如果没有被引用,直接删除
int b =baseMapper.deleteById(id);
if(b<1){
return Result.error("删除供应商失败");
}
return Result.ok();
}
需要调用商品业务层的方法(查看供应商是否与商品相关联,注意:IGoodsService不是 GoodsServiceImpl),调用的是方法,而不是关注方法是如何实现的;
// 删除供应商
@DeleteMapping("/{id}")
public Result delete(@PathVariable("id") int id){
return supplierService.deleteSupplier(id);
}
单表查询,只需要控制层
// 查询供应商
@GetMapping("/{id}")
public Result GetById(@PathVariable("id") int id){
Supplier supplier=supplierService.getById(id);
return Result.ok(supplier);
}
测试:
需要id和修改的供应商内容
1.编写业务层
// 修改供应商
Result updateSupplier(int id,Supplier supplier);
2.实现业务层
// 修改供应商
@Override
public Result updateSupplier(int id, Supplier supplier) {
if(supplier.getId()==null){
supplier.setId(id);
}
int b= baseMapper.updateById(supplier);
if(b<1){
return Result.error("修改供应商失败");
}
return Result.ok();
}
3.测试
商品列表要显示供应商名称,而在 tb_goods 表中只有供应商id,要显示供应商名称就要tb_goods 关联tb_supplier 表查询。
package com.cc.member.req;
import lombok.Data;
import java.io.Serializable;
@Data
public class GoodsREQ implements Serializable {
/**
* 商品名称
*/
private String name;
/**
* 商品编码
*/
private String code;
/**
* 供应商id
*/
private String supplierId;
}
tb_goods 关联 tb_supplier 表条件查询商品分页数据
1.GoodsMapper 接口中定义 searchPage 方法
package com.cc.member.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.cc.member.entity.Goods;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cc.member.req.GoodsREQ;
import org.apache.ibatis.annotations.Param;
/**
*
* 商品信息表 Mapper 接口
*
*
* @author cc-www.cc.com
* @since 2023-05-18
*/
public interface GoodsMapper extends BaseMapper {
/**
* 不需要手动去分页,而mybaits-plus会自动实现分页
* 但是你必须第1个参数传入IPage对象,第2个参数通过 @Param 取别名,
* 最终查询到的数据会被封装到IPage实现里面
* @param page
* @param req
* @return
*/
IPage searchPage(IPage page, @Param("req") GoodsREQ req);
}
2.在 com/mengxuegu/member/mapper/xml/GoodsMapper.xml 添加查询的sql实现
3.检查 application.yml 中的包名是否正确(根据自己创建的文件目录比对)
4.上面sql查询结果有 supplierName 商品名称,我们在 com.mengxuegu.member.entity.Goods 类添加一个supplierName属性
/**
* 供应商名称
*/
// 标识它不是tb_goods表中字段,不然会报错
@TableField(exist = false)
private String supplierName;
1.编写业务层
// 分页查询商品
Result search(int page, int size, GoodsREQ req);
2.实现业务层
// 分页查询
@Override
public Result search(int page, int size, GoodsREQ req) {
// 如果请求条件为空,就全部查询
if(req==null){
req=new GoodsREQ();
}
IPage p=new Page<>(page,size);
IPage data=baseMapper.searchPage(p,req);
return Result.ok(data);
}
package com.cc.member.controller;
import com.cc.member.base.Result;
import com.cc.member.req.GoodsREQ;
import com.cc.member.service.IGoodsService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
*
* 商品信息表 前端控制器
*
*
* @author cc-www.cc.com
* @since 2023-05-18
*/
@RestController
@RequestMapping("/goods")
public class GoodsController {
Logger logger= LoggerFactory.getLogger(getClass());
@Autowired
private IGoodsService iGoodsService;
// 分页查询
@PostMapping("/list/search/{page}/{size}")
public Result search(@PathVariable("page") int page,@PathVariable("size") int size, @RequestBody GoodsREQ req){
logger.info("分页数据,page={},size={}",page,size);
return iGoodsService.search(page,size,req);
}
}
新增与删除商品只要对 tb_goods 单表操作,我们可以直接使用 mybatis-plus 提供的 IMemberService 方法进行操作即可。
添加控制层方法
// 新增商品
@PostMapping
public Result add(@RequestBody Goods goods){
boolean b= iGoodsService.save(goods);
if(b){
return Result.ok();
}
return Result.error("新增商品失败");
}
// 删除商品
@DeleteMapping("/{id}")
public Result delete(@PathVariable("id") int id){
boolean b= iGoodsService.removeById(id);
if(b){
return Result.ok();
}
return Result.error("删除失败");
}
测试
添加方法
// 查询商品
Result findById(int id);
// 修改商品
Result update(int id,Goods goods);
@Autowired
private ISupplierService iSupplierService;
// 查询商品
@Override
public Result findById(int id) {
// 通过id查询到商品
Goods goods=baseMapper.selectById(id);
// 如果有供应商,则将供应商的名字查询出来
if(goods!= null && goods.getSupplierId()!=null ){
Supplier supplier=iSupplierService.getById(goods.getSupplierId());
if(supplier!=null){
goods.setSupplierName(supplier.getName());
}
}
return Result.ok(goods);
}
// 修改商品
@Override
@PutMapping("/{id}}")
public Result update(@PathVariable("id") int id,@RequestBody Goods goods) {
if(goods.getId()==null){
goods.setId(id);
}
int b=baseMapper.updateById(goods);
if(b<1){
return Result.error("修改失败");
}
return Result.ok();
}
// 查询商品
@GetMapping("/{id}")
public Result find(@PathVariable("id") int id){
return iGoodsService.findById(id);
}
// 修改商品
@PutMapping("/{id}")
public Result update(@PathVariable("id") int id,@RequestBody Goods goods){
return iGoodsService.update(id,goods);
}
package com.cc.member.req;
import lombok.Data;
import java.io.Serializable;
@Data
public class StaffREQ implements Serializable {
private String name;
private String username;
}
public interface IStaffService extends IService {
// 员工分页查询
Result search(long page, long size, StaffREQ req);
}
2.实现方法
// 分页条件查询
@Override
public Result search(long page, long size, StaffREQ req) {
QueryWrapper query=new QueryWrapper();
if(req!=null){
if(StringUtils.isNotBlank(req.getName())){
query.like("name",req.getName());
}
if(StringUtils.isNotBlank(req.getUsername())){
query.like("username",req.getUsername());
}
}
IPage p=new Page<>(page,size);
IPage data=baseMapper.selectPage(p,query);
return Result.ok(data);
}
Logger logger= LoggerFactory.getLogger(getClass());
@Autowired
private StaffServiceImpl staffService;
// 员工分页接口
@PostMapping("/list/search/{page}/{size}")
public Result search(@PathVariable("page") long page,@PathVariable("size") long size,@RequestBody(required = false) StaffREQ req){
logger.info("分页数据。page={},size={}",page,size);
return staffService.search(page,size,req);
}
注意:@RequestBody(required=false) 表示查询条件可以为空
需求:
// 新增员工
Result add(Staff staff);
2.实现方法
// 新增员工
@Override
public Result add(Staff staff) {
// 1.新增员工不允许为空
if(staff==null || StringUtils.isEmpty(staff.getUsername())){
return Result.error("用户名不允许为空");
}
// 2.查询用户名是否存在,存在不允许添加
Staff s=getUserName(staff.getUsername());
if(s!=null){
return Result.error("用户名已存在");
}
// 3.对密码进行保密
String pas=new BCryptPasswordEncoder().encode(staff.getPassword());
staff.setPassword(pas);
// 4.保存到数据库
boolean b=this.save(staff);
if(b){
return Result.ok();
}
return Result.error("添加失败");
}
// 通过用户名查询staff
public Staff getUserName(String username){
QueryWrapper query=new QueryWrapper();
query.eq("username",username);
return baseMapper.selectOne(query);
}
// 添加员工
@PostMapping
public Result add(@RequestBody Staff staff){
return staffService.add(staff);
}
删除员工只要对 tb_staff 单表操作,我们可以直接使用 mybatis-plus 提供的 IStaffService 方法进行操作即可。
添加控制层
/ 删除员工
@DeleteMapping("/{id}")
public Result delete(@PathVariable("id") int id){
boolean b=staffService.removeById(id);
if(b){
return Result.ok();
}
return Result.error("删除失败");
}
测试
1. 删除员工 ,发送 DELETE 请求 localhost:6666/staff/2
添加控制层
// 查询员工
@GetMapping("/{id}")
public Result select(@PathVariable("id") int id){
Staff staff= staffService.getById(id);
return Result.ok(staff);
}
// 修改员工
@PutMapping("/{id}")
public Result update(@PathVariable("id") int id,@RequestBody Staff staff){
return staffService.update(id,staff);
}
添加业务层
1.添加方法
// 修改员工
Result update(int id,Staff staff);
2.实现方法
// 修改员工
@Override
public Result update(int id, Staff staff) {
if(staff.getId()==null){
staff.setId(id);
}
int b= baseMapper.updateById(staff);
if(b<1){
return Result.error("修改失败");
}
return Result.ok();
}
测试
1. 修改员工,发送 PUT 请求 localhost:6666/staff/2
需求:
package com.cc.member.req;
import lombok.Data;
@Data
public class PasswordREQ {
/**
* 用户id
*/
private Integer userId;
/**
* 原密码 or 新密码
*/
private String password;
}
1.编写方法
// 检查原始密码是否正确
Result checkPassword(PasswordREQ passwordREQ);
// 更新密码
Result updatePassword(PasswordREQ passwordREQ);
2.实现方法
// 检查原始密码是否正确
@Override
public Result checkPassword(PasswordREQ passwordREQ) {
if(passwordREQ == null || StringUtils.isEmpty(passwordREQ.getPassword())) {
return Result.error("原密码不能为空");
}
Staff staff=baseMapper.selectById(passwordREQ.getUserId());
if(!new BCryptPasswordEncoder().matches(passwordREQ.getPassword(),staff.getPassword())){
return Result.error("原始密码错误");
}
return Result.ok();
}
// 更新密码
@Override
public Result updatePassword(PasswordREQ passwordREQ) {
if(passwordREQ == null || StringUtils.isEmpty(passwordREQ.getPassword())) {
return Result.error("新密码不能为空");
}
Staff staff=baseMapper.selectById(passwordREQ.getUserId());
String pas=new BCryptPasswordEncoder().encode(passwordREQ.getPassword());
staff.setPassword(pas);
int b=baseMapper.updateById(staff);
if(b<1){
return Result.error("更新失败");
}
return Result.ok();
}
因为 修改密码URL是 /user 开头的,而StaffController 是 /staff 开头,我们就创建一个的控制类 AuthController
package com.cc.member.controller;
import com.cc.member.base.Result;
import com.cc.member.req.PasswordREQ;
import com.cc.member.service.impl.StaffServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
public class AuthController {
@Autowired
private StaffServiceImpl staffService;
// 检查密码
@PostMapping("/pwd")
public Result check(@RequestBody PasswordREQ req){
return staffService.checkPassword(req);
}
// 修改密码
@PutMapping("/pwd")
public Result updatePwd(@RequestBody PasswordREQ passwordREQ){
return staffService.updatePassword(passwordREQ);
}
}
JSON Web Token( JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用 户和服务器之间传递安全可靠的信息。其中 JWT 中可以包含用户信息。
io.jsonwebtoken
jjwt
0.6.0
2.如果使用JDK9还要添加以下依赖
默认情况下,在java SE 9.0 中 将不再包含java EE 的Jar包,而 JAXB API是java EE 的API,因此我们要手动导入这个 Jar 包 。
io.jsonwebtoken
jjwt
0.6.0
javax.xml.bind
jaxb-api
2.3.0
com.sun.xml.bind
jaxb-impl
2.3.0
com.sun.xml.bind
jaxb-core
2.3.0
javax.activation
activation
1.1.1
1.在 mengxuegu-member-util 模块中创建工具类 com.mengxuegu.member.util.JwtUtil
2.JwtUtil 代码实现如下:
package com.cc.member.base;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
@ConfigurationProperties(prefix = "cc.jwt.config")
public class JwtUtil {
// 密钥
private String secretKey;
//单位秒,默认7天
private long expires = 60*60*24*7;
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public long getExpires() {
return expires;
}
public void setExpires(long expires) {
this.expires = expires;
}
/**
* 生成JWT
* @param id
*/
public String createJWT(String id, String subject, Boolean isLogin) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder().setId(id) //字符串
.setSubject(subject) //主题 如用户名
.setIssuedAt(now) //签发时间
.signWith(SignatureAlgorithm.HS256, secretKey) //签名密钥
.claim("isLogin", isLogin);
if (expires > 0) {
// expires乘以1000是毫秒转秒
builder.setExpiration(new Date(nowMillis + expires*1000));
}
return builder.compact();
}
/**
* 解析JWT
* @param jwtToken
*/
public Claims parseJWT(String jwtToken){
return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtToken).getBody();
}
}
3.在 mengxuegu-member-api 模块的 application.yml 中添加配置
cc:
jwt:
config:
secretKey: mengxuegu # jwt令牌密钥
expires: 604800 # 单位秒,7天
1.添加方法
Result login(String username, String password);
2.实现方法
@Autowired
JwtUtil jwtUtil;
// 登录接口
@Override
public Result login(String username, String password) {
// 用户名和密码不能为空
if(StringUtils.isBlank(username)
|| StringUtils.isBlank(password)) {
return Result.error("用户名或密码错误");
}
// 1. 通过用户名查询
Staff staff = getUserName(username); if(staff == null) {
return Result.error("用户名或密码错误");
}
// 2. 存在,判断密码是否正确(输入的密码,数据库加密的密码)
if( !new BCryptPasswordEncoder().matches(password, staff.getPassword())) {
return Result.error("用户名或密码错误");
}
// 3. 生成token 响应
String jwt = jwtUtil.createJWT(staff.getId() + "", staff.getUsername(), true);
// 手 动 封 装 个 json 对 象 {token: jwt}
Map map = new HashMap<>();
map.put("token", jwt);
return Result.ok(map);
}
// 通过用户名查询staff
public Staff getUserName(String username){
QueryWrapper query=new QueryWrapper();
query.eq("username",username);
return baseMapper.selectOne(query);
}
/**
* 登录
* @return
*/
@PostMapping("/login")
public Result login(@RequestBody Staff staff) {
return staffService.login(staff.getUsername(), staff.getPassword());
}
1.创建方法
/**
* 通过token获取用户信息
* @param token
* @return
*/
Result getUserInfo(String token);
@Override
public Result getUserInfo(String token) {
// 解析jwt
Claims claims = jwtUtil.parseJWT(token);
if(claims == null || StringUtils.isBlank(claims.getSubject())) {
return Result.error("获取用户信息失败");
}
// 获取用户名
String username = claims.getSubject();
// 1. 通过用户名查询
Staff staff = getUserName(username);
if(staff == null) {
return Result.error("用户不存在");
}
// 2. 将密码设置为null,不响应给前端
staff.setPassword(null);
return Result.ok(staff);
}
// 通过用户名查询staff
public Staff getUserName(String username){
QueryWrapper query=new QueryWrapper();
query.eq("username",username);
return baseMapper.selectOne(query);
}
jwt令牌无法手动让它失效,在前端点击退出时直接删除localStorage中的数据,如果需要可以使用将jwt通过redis存储,每次请求时从redis查询是否存在,如果不存在,则认为未登录已退出。
只需要添加控制层
/**
* 退出
* @return
*/ @PostMapping("/logout")
public Result logout() {
return Result.ok();
}
我们自定一个拦截器,只有当用户登录后,才可以访问资源接口(会员、商品、供应商、员工),没有登录则要求 登录。
其中判断是否登录,要求客户端请求接口时,在请求头上带上 token ,然后在拦截器拦截到请求后,校验 token是否有效,有效才让访问,否则无法访问。
请求头信息 Authorization: Bearer jwtToken
package com.cc.member.filter;
import com.cc.member.base.JwtUtil;
import io.jsonwebtoken.Claims;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 认证拦截器
*/
@Component
public class AuthenticationFilter extends HandlerInterceptorAdapter {
@Autowired
private JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 是否登录
boolean isLogin = false;
// 请求头带上令牌 Authorization: Bearer jwtToken
final String authHeader = request.getHeader("Authorization");
if(StringUtils.isNotBlank(authHeader) && authHeader.startsWith("Bearer ")) {
// 截取token,
final String token = authHeader.substring(7);
// 解析token
Claims claims = jwtUtil.parseJWT(token);
if(claims != null) {
// 是否登录
Boolean b = (Boolean) claims.get("isLogin");
if(b) {
// 已经登录,放行请求
isLogin = true;
}
}
}
if(!isLogin) {
// 未登录,则响应信息
response.setContentType("application/json;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
response.setStatus(401);
response.getWriter().write("未通过认证,请在登录进行登录");
}
// 不放行
return isLogin;
}
}
1. 创建一个配置类 com.mengxuegu.member.config.WebMvcConfig 其中要放行登录请求 /user/login
package com.cc.member.config;
import com.cc.member.filter.AuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Autowired
private AuthenticationFilter authenticationFilter;
protected void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(authenticationFilter).
// 拦截所有请求
addPathPatterns("/**").
// 登录请求排除,不被拦截
excludePathPatterns("/user/login");
}
}
3.针对 mengxuegu-member-api 执行 clean 和install
5.先执行 java -jar 打包好的名字.jar 使用postman,仍然可以测试
6.不报错,则以后台进程方式启动