赞
踩
项目中的问题:
后端在返回员工信息时,由于员工的id是Long类型,长度达到了19位,而前端JS能够处理的整数运算最多为正负2的53次方,也就是16位,也即从最小的值-9007199254740992到最大的值9007199254740992之间的范围,如果超过这个范围会进行四合五入,造成精度损失。
所以前端拿到的id是不准确的,如果我们后续修改用户时用的是这个不准确的id,必然是无法查询到数据的,所以为了解决这个问题,我们配置了一个Java对象和Json数据的转换器,在后端返回数据时,就将Long类型的id,转换为字符串,这样前端拿到的id就不会存在精度损失。
配置:
package com.dong.reggie.commom; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; import java.math.BigInteger; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; /** * 对象映射器:基于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) .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); } } /// //在继承了WebMvcConfigurationSupport 类中重写extendMessageConverters方法,在该方法中指定我们的对象转换器。 package com.dong.reggie.config; import com.dong.reggie.commom.JacksonObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import java.util.List; @Slf4j @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { log.info("开启静态资源映射"); registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/"); registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/"); } @Override protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) { log.info("扩展消息转换器"); //创建消息转换器 MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter=new MappingJackson2HttpMessageConverter(); //设置对象转换器 mappingJackson2HttpMessageConverter.setObjectMapper(new JacksonObjectMapper()); //将上面的消息转换器追加到MVC框架的转换器中 converters.add(0,mappingJackson2HttpMessageConverter); } }
我们在对用户进行修改,添加时都会对一些字段进行修改,比如创建时间、修改时间、创建人、修改人等等,凡是大量的在插入或者修改数据库时,我们都需要手动进行设置之后在执行插入或者修改的字段,都可以理解为公共字段。
问题:如果每一次添加、修改的请求都需要对这些字段进行设置,会产生大量的冗余代码,而且会很浪费时间,重复性的操作太多。
解决方案:使用Mybatis-puls框架解决,实现MetaObjectHandler类
实现:
package com.dong.reggie.commom; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.time.LocalDateTime; @Component public class MyMetObjectHandler implements MetaObjectHandler { /** * 添加数据,自动填充 * @param metaObject //对应我们要操作的对象 */ @Override public void insertFill(MetaObject metaObject) { metaObject.setValue("createTime", LocalDateTime.now()); metaObject.setValue("updateTime",LocalDateTime.now()); //如何获取当前用户的id? //使用ThreadLocal,ThreadLocal可以理解每一个线程单独的存储空间 //在登录拦截中的doFilter,将当前用户的id存入ThreadLocal,在该方法中就可以使用threadlocal去获取该id //实现步骤,封装一个ThreadLocal工具类,在登录拦截器中保存id到ThreadLocal,获取id /** public class BaseoCntext { private static ThreadLocal<Long> threadLocal=new ThreadLocal<>(); public static void setCurrentId(Long id){ threadLocal.set(id); } public static Long getCurrentId() { return threadLocal.get(); } } */ metaObject.setValue("createUser",BaseoCntext.getCurrentId()); metaObject.setValue("updateUser",BaseoCntext.getCurrentId()); } /** * 修改数据、自动填充 * @param metaObject */ @Override public void updateFill(MetaObject metaObject) { metaObject.setValue("updateTime",LocalDateTime.now()); metaObject.setValue("updateUser",BaseoCntext.getCurrentId()); } }
最后一步,在实体类中需要自动填充的字段,添加注解相应注解,即可在进行插入、修改操作之前自动填充数据。
对于前端:
对于后端:
文件下载:
文件上传服务端代码:
package com.dong.reggie.controller; import com.dong.reggie.commom.R; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.util.UUID; @Slf4j @RestController @RequestMapping("/common") public class CommonCtroller { @Value("${reggie.path}") private String basePath; @PostMapping("/upload") public R<String> uplod(MultipartFile file){ log.info("接收到图片上传",file); // 截取原始文件名称的类型 String originalFilename = file.getOriginalFilename(); String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); //使用uuid生成新的文件名称,防止名称重复覆盖 String fileName = UUID.randomUUID().toString()+suffix; //如果目录不存在,则创建目录 File dir=new File(basePath); if(!dir.exists()){ dir.mkdirs(); } try { //转存接收到文件 file.transferTo(new File(basePath+fileName)); } catch (IOException e) { e.printStackTrace(); } return R.success(fileName); } }
服务器端文件下载:
@GetMapping("/download") //name为文件名称 public void downLoad(HttpServletResponse response, String name) { FileInputStream in = null; ServletOutputStream out = null; try { //构建文件输入流,读取保存的图片 in = new FileInputStream(new File(basePath + name)); //构建输出流,输出图片信息 out = response.getOutputStream(); //设置响应流格式 response.setContentType("image/jpeg"); //写入图片数据 int len = 0; byte[] bytes = new byte[1024]; //读入数据到byte数组 while ((len = in.read(bytes)) != -1) { //从byte数组写出数据 out.write(bytes, 0, len); out.flush(); } } catch (Exception e) { e.printStackTrace(); } finally { //关闭流 if(in!=null){ try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if(out!=null){ try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } }
依赖
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.16</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>2.1.0</version>
</dependency>
短信发送工具类
package com.dong.reggie.utils; import com.aliyuncs.DefaultAcsClient; import com.aliyuncs.IAcsClient; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest; import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse; import com.aliyuncs.exceptions.ClientException; import com.aliyuncs.profile.DefaultProfile; /** * 短信发送工具类 */ public class SMSUtils { /** * 发送短信 * @param signName 签名 * @param templateCode 模板 * @param phoneNumbers 手机号 * @param param 参数 */ public static void sendMessage(String signName, String templateCode,String phoneNumbers,String param){ DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "", ""); IAcsClient client = new DefaultAcsClient(profile); SendSmsRequest request = new SendSmsRequest(); request.setSysRegionId("cn-hangzhou"); request.setPhoneNumbers(phoneNumbers); request.setSignName(signName); request.setTemplateCode(templateCode); request.setTemplateParam("{\"code\":\""+param+"\"}"); try { SendSmsResponse response = client.getAcsResponse(request); System.out.println("短信发送成功"); }catch (ClientException e) { e.printStackTrace(); } } }
生成四位数字验证码工具类
package com.dong.reggie.utils; import java.util.Random; /** * 随机生成验证码工具类 */ public class ValidateCodeUtils { /** * 随机生成验证码 * @param length 长度为4位或者6位 * @return */ public static Integer generateValidateCode(int length){ Integer code =null; if(length == 4){ code = new Random().nextInt(9999);//生成随机数,最大为9999 if(code < 1000){ code = code + 1000;//保证随机数为4位数字 } }else if(length == 6){ code = new Random().nextInt(999999);//生成随机数,最大为999999 if(code < 100000){ code = code + 100000;//保证随机数为6位数字 } }else{ throw new RuntimeException("只能生成4位或6位数字验证码"); } return code; } /** * 随机生成指定长度字符串验证码 * @param length 长度 * @return */ public static String generateValidateCode4String(int length){ Random rdm = new Random(); String hash1 = Integer.toHexString(rdm.nextInt()); String capstr = hash1.substring(0, length); return capstr; } }
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置Redis
redis:
port: 6379
host: localhost
database: 0
配置类,设置序列化器
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory connectionFactory){
RedisTemplate<Object,Object> redisTemplate=new RedisTemplate<>();
//设置Redis Key的默认序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(connectionFactory);
return redisTemplate;
}
}
在相应的业务逻辑中,使用缓存
//保存验证码,缓存到Redis中
redisTemplate.opsForValue().set(phone,code,5,TimeUnit.MINUTES);
//从redis中取出数据
redisTemplate.opsForValue().get(phone);
//从redis中删除数据
redisTemplate.delete(phone);
注意:如果项目中使用了缓存,一定要注意数据库和缓存中的一致性问题,该项目中解决该问题的处理方式比较简单,不够全面。
使用Spring Cache,底层使用Redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
在启动类上添加注解@EnableCaching注解,开启缓存注解。在Controller方法上添加@Cacheable,@CacheEvict等注解
配置多个数据库,一个为写库,一个为读库,降低单台数据库的访问压力。需要解决不同数据库之间的数据同步问题。
主库为写库,从库为读库
MySQL的主从复制实际上就是其他的数据库(从库)获取一台数据库(主库)的日志,并对该日志解析然后自己照着解析的日志操作一遍,比如解析出两条insert、一条update语句,那从库也会去执行这些语句,从而达到从库和主库数据同步的效果,并且主从复制是MySQL自带的功能,不需要借助第三方工具。
MySQL复制过程分成三步
想要配置主从配置,毫无疑问,需要两个mysql,也就说需要两个服务器,这里我使用Linux中的mysql,来实现主从复制
主库IP:192.168.248.110
从库ip:192.168.248.111
主库:
1、修改master数据库的配置文件/etc/my.cnf
log-bin=mysql-bin #启用二进制文件
server-id=100 #服务器唯一id
2、重启mysql服务 systemctl restart mysqld
3、创建一个用于从库和主库通信的用户
执行下面的sql
grant replication slave on *.* to ‘xiaoming’@'%' identified by ‘Root@123456’;
该sql的作用是创建一个用户xiaoming,密码为Root@123456,并且给xiaoming用户授予replication slave权限。slave必须被master授予具有该权限的用户,才能通过该用户去进行复制。
4、
从库:
1
2.重启服务
3
4
若配置完,发现slave_id_running为 no,这些因为克隆的原因,导致两个mysql的uuid相同。
此时,删除任意一个mysql的autu.cnf文件,让它从新生成新的uuid即可。
rm -rf /var/lib/mysql/auto.cnf
删除之后记得重启服务,建议连个mysql都重启一次 stytemctl restart mysqld
之后等一会再次在slave中运行 show slave status,就会发现slave_id_running变为 yes。
此时mysql的主从复制配置已经完成,可以在主库中添加数据测试一下从库是否同步。
Sharding-JDBC
Sharding-JDBC定位为轻量级java框架、在java的jdbc层提供额外的服务。它能使客户端直连数据库。以jar包的形式提供服务,无需额外部署和依赖,可理解为增强版的jdbc驱动,完全兼容jdbc和各种orm框架。
使用Sharding-JDBC可以在程序中轻松的实现读写分离。
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.0-RC1</version>
</dependency>
使用Sharding-jdbc实现读写分离
1、导入Maven坐标
2、在配置文件中配置读写分离规则
3、在配置文件中配置允许Bean自定义覆盖配置项
server: port: 8081 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: E:\reggieImg\ spring: application: name: reggie_take_out redis: port: 6379 host: localhost database: 0 cache: redis: time-to-live: 1800000 #设置缓存有效期 ##配置读写分离 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.248.110:3306/reggie?characterEncoding=utf-8 username: root password: ****** # 从数据源 slave: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://192.168.248.111:3306/reggie?characterEncoding=utf-8 username: root password: ******** 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
启动项目就可以看见其创建了两个数据源
至此,我们就实现了读写分离,我们的java源代码不需要进行任何修改。
可以测试一下:
查询:可以看到查询走的是我们的从节点,也及时slave
新增:
安装环境
yum -y install gcc pcre-devel zlib-devel openssl openssl-devel
从官网下载NGINX 官网地址:nginx.org
安装完成。
Nginx命令:
命令 | 功能 |
---|---|
./nginx -v | 查看版本号 |
./nginx -t | 检查配置文件conf/nginx.conf是否存在错误 |
./nginx | 启动nginx |
./nginx -s stop | 停止nginx服务 |
ps -ef | grep nginx | 查看ngin相关的进程 |
./nginx -s reload | 重新加载配置文件 |
配置环境变量 vi /etc/profile
然后从新加载一下配置文件 source /etc/profile
用于定义接口
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
配置knife4j
package com.itheima.reggie.config; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j; import com.itheima.reggie.common.JacksonObjectMapper; import com.itheima.reggie.entity.Employee; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.math.BigInteger; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.util.List; import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; @Slf4j @Configuration @EnableSwagger2 @EnableKnife4j public class WebMvcConfig extends WebMvcConfigurationSupport { /** * 设置静态资源映射 * @param registry */ @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { log.info("开始进行静态资源映射..."); //设置静资源 registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/"); registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/"); registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/"); registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/"); } /** * 扩展mvc框架的消息转换器 * @param converters */ @Override protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) { log.info("扩展消息转换器..."); //创建消息转换器对象 MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter(); //设置对象转换器,底层使用Jackson将Java对象转为json messageConverter.setObjectMapper(new JacksonObjectMapper()); //将上面的消息转换器对象追加到mvc框架的转换器集合中 converters.add(0,messageConverter); } //配置Swagger @Bean public Docket createRestApi() { // 文档类型 return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .select() .apis(RequestHandlerSelectors.basePackage("com.dong.reggie.controller")) .paths(PathSelectors.any()) .build(); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("瑞吉外卖") .version("1.0") .description("瑞吉外卖接口文档") .build(); } }
放行路径
启动项目,即可看见接口文档
注解 | 说明 |
---|---|
@Api | 用在请求类上,例如Controller,对类进行说明 |
@ApiModel | 用在类上,通常是实体类,表示返回响应的数据信息 |
@ApiModelProperty | 用在属性上,描述响应类的属性 |
@ApiOperation | 用在请求的方法上,说明方法的用途、作用 |
@ApiImplicitParams | 用在请求的方法上,表示一组参数的说明 |
@ApiImplicitParam | 用在@ApiImplicitParams注解中,指定一个请求参数的各个方面 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。