com.baomidou
mybatis-plus-generator
3.5.1
org.freemarker
freemarker
2.3.31
public class MybatisPlusCodeTest {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://localhost:3306/reggie?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false", "root", "1234")
.globalConfig(builder -> {
// 设置作者
builder.author("luge")
// 开启 swagger 模式
//.enableSwagger()
// 覆盖已生成文件
.fileOverride()
// 指定输出目录
.outputDir("C://Users//ASUS//Desktop//mybatis_plus");
})
.packageConfig(builder -> {
// 设置父包名
builder.parent("pers.jl")
// 设置mapperXml生成路径
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "C://Users//ASUS//mybatis_plus"));
})
.strategyConfig(builder -> {
// 设置需要生成的表名
builder.addInclude("address_book")
.addInclude("category")
.addInclude("dish")
.addInclude("dish_flavor")
.addInclude("employee")
.addInclude("order_detail")
.addInclude("orders")
.addInclude("setmeal")
.addInclude("setmeal_dish")
.addInclude("shopping_cart")
.addInclude("user")
;
})
// 使用Freemarker引擎模板,默认的是Velocity引擎模板
.templateEngine(new FreemarkerTemplateEngine())
.execute();
}
}
/**
* 检查用户是否已经完成登录
*/
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter{
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 强转一下
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
//1、获取本次请求的URI
String requestURI = request.getRequestURI();// /backend/index.html
//定义不需要处理的请求路径
String[] urls = new String[]{
// controller层的请求
"/employee/login",
"/employee/logout",
// 静态资源
"/backend/**",
"/front/**"
};
//2、判断本次请求是否需要拦截处理
boolean check = Utils.check(urls, requestURI);
//3、如果不需要处理,则直接放行
if(check){
filterChain.doFilter(request,response);
// 退出方法
return;
}
//4、如果请求资源需要登录才能访问,则要判断登录状态
// 如果已登录,则直接放行
if(request.getSession().getAttribute("employeeId") != null){
filterChain.doFilter(request,response);
return; }
log.info("用户未登录");
//5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据
// 根据前端的响应拦截器接收结果来定义的
response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));
return; }
}
/**
* 全局异常处理类
*/
@ControllerAdvice
@ResponseBody// 响应json数据
@Slf4j
public class GlobalExceptionHandler {
/**
* 当新增员工,姓名重复时,mysql会报唯一性错误,这个方法会进行处理
* 使用Exception相比SQLIntegrityConstraintViolationException范围更大,但是不利于拦截种指定异常,不推荐
* SQLIntegrityConstraintViolationException更细,指定处理该类异常,异常消息也更少。
* @ExceptionHandler 这个注解对所有加了@RequestMapping()的类抛出的异常,都会处理
* @param ex
* @return
*/
@ExceptionHandler(SQLIntegrityConstraintViolationException.class)
public R exceptionHandler(SQLIntegrityConstraintViolationException ex){
log.error(ex.getMessage());
if(ex.getMessage().contains("Duplicate entry")){
String[] split = ex.getMessage().split(" ");
String msg = split[2] + "已存在";
return R.error(msg);
}
return R.error("未知错误");
}
/**
* 用户删除分类失败时返回错误信息
* @return
*/
@ExceptionHandler(MyException.class)
public R exceptionHandler(MyException ex){
// 打印异常日志信息
log.error(ex.getMessage());
// 将异常信息展示给用户,这个信息是我们提前写好的
return R.error(ex.getMessage());
}
}
分页逻辑:每次到达员工管理页面时,会自动发送一个get的分页请求,携带page和pagesise参数,我们需要创建page对象,然后传入这两个参数,如果页面输入姓名查询,我们还需要构建一个条件构造器,由于页面需要分页对象,所以最后返回分页对象即可。
编写一个mybatisplus的配置类,配置一个mybatisplus拦截器并添加分页拦截器。
注意事项:controller接收参数时,如果是对象中的部分属性,选择用对象来接收会好很多。
webmvc配置类
@Slf4j
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
/**
* 扩展mvc框架的消息转换器
* @param converters
*/
@Override
protected void extendMessageConverters(List> converters) {
log.info("扩展消息转换器...");
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将Java对象转为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到mvc框架的转换器集合中
converters.add(0,messageConverter);
}
}
对象转换器
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
// 反序列化
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
// 序列化
.addSerializer(BigInteger.class, ToStringSerializer.instance)
.addSerializer(Long.class, ToStringSerializer.instance)// Long型数据转成String类型发给前端
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
新增和编辑员工功能加强
// 最近更新时间和最近更新用户采用新增和修改都赋值的策略
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Long updateUser;
// 创建用户和创建时间只需要在创建时进行赋值即可
@TableField(fill = FieldFill.INSERT)
private Long createUser;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
/**
* 元数据对象处理器
* 公共字段自动填充功能实现
* 对元数据进行处理
* 对字段自动填充赋值
* 凡是对员工的新增或更新操作都会自动填充
*/
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
// 新增操作
@Override
public void insertFill(MetaObject metaObject) {
log.info("公共字段自动填充");
// 设置公共字段自动填充
metaObject.setValue("createTime", LocalDateTime.now());
metaObject.setValue("updateTime", LocalDateTime.now());
// 通过工具类,从线程变量中获取登录用户id
metaObject.setValue("createUser", BaseContext.getCurrentId());
metaObject.setValue("updateUser", BaseContext.getCurrentId());
}
// 更新操作
@Override
public void updateFill(MetaObject metaObject) {
metaObject.setValue("updateTime", LocalDateTime.now());
metaObject.setValue("updateUser", BaseContext.getCurrentId());
}
}
/**
* 基于ThreadLocal封装工具类,用户保存和获取当前登录用户id
* 不需要对象,所以不用交给spring管理
* 都是静态的属性和方法
*/
public class BaseContext {
// 线程中的这个对象,把他new出来,可以向里面存入东西。
private static final ThreadLocal threadlocal = new ThreadLocal<>();
// 将登录用户id存入线程变量中
public static void setCurrentId(Long id){
threadlocal.set(id);
}
// 从线程变量中取出登录用户id
public static Long getCurrentId(){
return threadlocal.get();
}
}
rest风格获取路径参数,只有get需要在注解后面跟上/{id},delete后面不用跟。
get需要使用@PathVariable注解,delete也不使用@PathVariable注解,delete不能从路径上获取参数,所以不使用这个注解。
@PathVariable注解表示从路径上获取参数。
因为携带数据有两种方式,post或put中的data和get中的路径参数。
@GetMapping("/{id}")
public R employeeEdit(@PathVariable Long id){
// 根据员工id查询员工信息
Employee employee = employeeService.getById(id);
if (employee != null) {
// 返回员工数据
return R.success(employee);
}
return R.error("员工不存在!");
}
@DeleteMapping
public R deleteCategory(Long id){
log.info(String.valueOf(ids));
// 调用自定义删除方法进行删除
categoryService.remove(ids);
return R.success("删除成功!");
}
与新增分类原理类似。
分类管理模块注意事项:
本质上也是发送请求,然后处理请求。
添加静态资源无法找到页面,可以clean一下,路径有缓存。
@RestController
@RequestMapping("/common")
@Slf4j
public class CommonController {
// 读取配置文件自定义配置,文件夹路径
@Value("${reggie.path}")
private String basePath;
/**
* 文件上传(图片)
* @param file
* @return
*/
@PostMapping("upload")
public R upload(MultipartFile file){
// 1.重新给文件命名
// 获取原来文件名
String originalFilename = file.getOriginalFilename();
// 截取文件后缀
String subfix = originalFilename.substring(originalFilename.lastIndexOf("."));
// 随机生成UUid,拼接成新文件名
String newname = UUID.randomUUID().toString() + subfix;
// 2.判断转存文件夹是否存在
File file1 = new File(basePath);
if (!file1.exists()) {
// 文件夹不存在,新建文件夹
file1.mkdirs();
log.info("文件创建成功。");
}
// 3.文件转存
try {
// 如果是在服务器上,我们可以把图片保存到一个linux文件夹下面
file.transferTo(new File(basePath + newname));
log.info("文件转存成功。");
} catch (IOException e) {
e.printStackTrace();
}
// 转存成功,返回文件名字,
// 因为待会图片展示需要下载,就是需要上传成功时返回文件名,它才知道要下载哪个图片
return R.success(newname);
}
/**
* 文件下载(图片)
* @param response
* @param name
* @return
*/
@GetMapping("/download")
public void download(HttpServletResponse response,String name){// 页面发来的请求就带有文件名,是在上传文件时返回给页面的。
log.info("开始下载图片"+name);
try {
// 1.从哪里获取文件
FileInputStream inputStream = new FileInputStream(new File(basePath+name));
// 2.下载文件到哪里
ServletOutputStream outputStream = response.getOutputStream();
// 3. 设置响应文件格式
response.setContentType("image/jpeg");
// 4. 读取文件,写到输出流中
int len = 0;
byte[] bytes = new byte[1024];
// .read(bytes)的意思就是一次性读取1024个数据,
// 返回值是一个int,就是返回的是读取的指针的下标,
// 当指针为-1时,就代表读取完成
// 边读边写
while ((len = inputStream.read(bytes)) != -1){
// 一次性写出1024个数据,从数组下标为0位置开始写入,写入的字节数为读取的文件字节长度。
outputStream.write(bytes,0,len);
// 用于清空输出流
outputStream.flush();
}
log.info("图片下载完毕"+name);
// 5. 关闭资源
inputStream.close();
outputStream.close();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
本项目将很多的方法都放在自定义的工具类中,代码如下。
工具类代码。
- yum list git 列出git安装包
- yum install git 在线安装git
- 官网下载tar.gz压缩包,解压maven
- 修改环境变量
- 重新加载配置文件 source /etc/profile
- 查看maven版本信息 mvn -version
- 给maven中的本地仓库地址设置一个目录
优化思路:
优化思路:
com.fasterxml.jackson.datatype
jackson-datatype-jsr310
2.13.0
Dish
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
@JsonDeserialize(using = LocalDateTimeDeserializer.class) // 反序列化
@JsonSerialize(using = LocalDateTimeSerializer.class) // 序列化
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@JsonDeserialize(using = LocalDateTimeDeserializer.class) // 反序列化
@JsonSerialize(using = LocalDateTimeSerializer.class) // 序列化
private LocalDateTime updateTime;
DishFlavor
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
@JsonDeserialize(using = LocalDateTimeDeserializer.class) // 反序列化
@JsonSerialize(using = LocalDateTimeSerializer.class) // 序列化
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@JsonDeserialize(using = LocalDateTimeDeserializer.class) // 反序列化
@JsonSerialize(using = LocalDateTimeSerializer.class) // 序列化
private LocalDateTime updateTime;
SpringSecurity + JWT + Redis
使用SpringSecurity框架来帮我们实现访问控制,使用token来完善登录功能。
1. 导入依赖
2. 获取数据库所有用户名和密码
3. 编写配置类
使用docker部署请点击这里
log-bin=mysql-bin # 开启二进制日志
server-id=100 #服务器唯一id
systemctl restart mysqld # 重启mysql服务
// 创建用户
create user 'jianglu'@'%' identified by 'jianglu1234';
// 授权
GRANT REPLICATION SLAVE ON *.* TO 'jianglu'@'%';
// 刷新权限
flush privileges;
// 查看主库状态
show master status;
//设置服务器唯一id
server-id=101 #服务器唯一id
// 配置从库
CHANGE MASTER TO
master_host='192.168.142.85',
master_user='jianglu',
master_password='jianglu1234',
master_port=3001,
master_log_file='binlog.000004',
master_log_pos=862;
// 启动从库
start slave;
// 查看从库状态
show slave status;
注:如果需要重新配置主从关系
# 重新配置主从流程,先停止,再重置从库连接主库配置
stop slave;
reset master;
再根据主库信息进行从库配置(SHOW MASTER STATUS;)
org.apache.shardingsphere
sharding-jdbc-spring-boot-starter
4.0.0-RC1
server:
port: 8080
spring:
application:
name: reggie
shardingsphere:
datasource:
names:
master,slave
# 主数据源
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.142.85:3001/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 1234
# 从数据源
slave:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.142.85:3002/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 1234
masterslave:
# 读写分离配置
load-balance-algorithm-type: round_robin #轮询
# 最终的数据源名称
name: dataSource
# 主库数据源名称
master-data-source-name: master
# 从库数据源名称列表,多个逗号分隔
slave-data-source-names: slave
props:
sql:
show: true #开启SQL显示,默认false
main:
allow-bean-definition-overriding: true
redis:
password: 123456
database: 0
host: localhost
port: 6379
cache:
redis:
time-to-live: 1800000 #设置缓存数据的过期时间为30分钟
mybatis-plus:
configuration:
#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: ASSIGN_ID
# 自定义文件路径存取配置
reggie:
# 将图片存放地址更改为服务器上的地址
path: 192.168.142.85/opt/img/
com.github.xiaoymin
knife4j-spring-boot-starter
3.0.2
@Slf4j
@Configuration
@EnableSwagger2
@EnableKnife4j
public class WebMvcConfig extends WebMvcConfigurationSupport {
/**
* 设置静态资源映射
* 如果没有webmvcConfig控制类,我们可以直接访问static下的静态资源,
* 但是如果配置了这个webmvcConfig控制类,spring无法自动定位到静态资源,所以必须设置静态资源映射。
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("开始进行静态资源映射...");
registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/static/backend/");
registry.addResourceHandler("/front/**").addResourceLocations("classpath:/static/front/");
// 添加Swagger文档静态资源映射
registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
/**
* 扩展mvc框架的消息转换器
* @param converters
*/
@Override
protected void extendMessageConverters(List> converters) {
log.info("扩展消息转换器...");
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将Java对象转为json
messageConverter.setObjectMapper(new JacksonObjectMapper());
//将上面的消息转换器对象追加到mvc框架的转换器集合中
converters.add(0,messageConverter);
}
// 注入文档对象
@Bean
public Docket createRestApi() {
// 文档类型
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("pers.jl.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("瑞吉外卖")
.version("1.0")
.description("瑞吉外卖接口文档")
.build();
}
}
"/doc.html",
"/webjars/**",
"/swagger-resources",
"/v2/api-docs"
//一键安装上面四个依赖
yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel
tar -zxvf nginx-1.24.0.tar.gz
//执行命令 考虑到后续安装ssl证书 添加两个模块
./configure --with-http_stub_status_module --with-http_ssl_module
/编译安装 (默认安装到/usr/local/nginx)
make & make install
./nginx
#反向代理配置,url重写
location ^~ /api/ {
rewrite ^/api/(.*)$ /$1 break;
proxy_pass http://192.168.142.85:8080;
}