黑马实战项目瑞吉外卖的总结

一. 瑞吉外卖项目总结

瑞吉外卖项目分为后台管理端和移动端(用户端).

主要核心技术是:springboot +mybatis-plus +redis +mysql

1. 后端Controller层返回结果统一封装的R对象

后端的controller层接收完前端的请求后,要返回什么样的结果是需要按情况变化的,但如果每一个controller返回的结果不一样,前端也要用不同的数据类型进行接收。为了避免麻烦,制定统一的controller层返回对象是很有必要的。

public class R<T> implements Serializable {

    private Integer code; //编码:1成功,0和其它数字为失败

    private String msg; //错误信息

    private T data; //数据

    private Map map = new HashMap(); //动态数据

    public static <T> R<T> success(T object) {
        R<T> r = new R<T>();
        r.data = object;
        r.code = 1;
        return r;
    }

    public static <T> R<T> error(String msg) {
        R r = new R();
        r.msg = msg;
        r.code = 0;
        return r;
    }

    public R<T> add(String key, Object value) {
        this.map.put(key, value);
        return this;
    }

}


2.定义静态资源映射关系

静态资源映射关系主要用于将前端请求的URI路径与后端服务器资源路径进行映射。

Reggie项目中的用途:springboot中静态资源是默认放在static目录下和template目录下的,如果你要把静态资源放在其它目录下,就必须配置静态资源映射关系。否则前端的请求URI将匹配不到资源。

示例:

后端代码:

@Configuration
public class MyWebMvcConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        //设置静态资源映射关系
        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
    }

}

3. 配置消息资源转换器

3.1 Reggie项目中遇到的问题

数据库的主键大都是由mybatis-plus的主键自动生成策略之雪花算法生成的,雪花算法生成的是一个Long类型的数字,而雪花算法生成的主键传输到前端的时候会出现精度丢失现象导致前端拿到的id和数据库中的id不一致。那么前端再发出请求无论是通过id查找数据还是修改数据都会因为id不一致而修改失败。

3.2 原理

后端使用64位存储长整数(long),最大支持9223372036854775807 2.前端的JavaScript使用53位来存放,最大支持9007199254740992,超过最大值的数, 可能会 出现问题(得到的溢出后的值);

3.3 解决方案

springboot前后端资源传输可以采用json格式字符串,我们可以添加消息资源转换器MessageConverters,将Long类型的数据序列化为字符串,添加后spring web mvc在处理controller返回值的时候会采用自定义的序列策略自动将Long/BigInt序列化为字符串,这样就可以解决Long类型数据精度丢失问题。

3.4 示例

MyWebMvcConfig:

@Configuration
public class MyWebMvcConfig extends WebMvcConfigurationSupport {
    /*
    * 拓展消息资源转换器
    * */

    @Override
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter messageConverter=new MappingJackson2HttpMessageConverter();
        messageConverter.setObjectMapper(new JacksonObjectMapper());
        //将我们自定义的消息转换器,添加进行集合中,并把优先级设置为最高
        converters.add(0,messageConverter);
    }
}

JacksonObjectMapper:

/**
 * 对象映射器:基于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);
    }
}

maven依赖

        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.10</version>
        </dependency>

fastjson jar包有一系列的Java对象和json对象之间的序列化器供我们使用。


4. Mybatis-Plus的使用

4.1 基本使用

通过mybatis-plus框架的使用,在Reggie项目的实践中,确实明显的提高的开发效率,不需要在像以往一样给mapper映射文件写单独的配置文件mapper.xml,可以用简单的LambdaQueryWrapper类和LambdaUpdateWrapper类构造查询条件或者修改条件就可以代替在xml配置文件中写sql语句,大大简化了开发,同时mapper接口和Service接口和实现类都只需要实现或继承框架指定的类就可以。

样例

application.yaml 进行mybatis-plus相关配置

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

mapper接口:

@Mapper
public interface DishMapper extends BaseMapper<Dish> {
}

service接口:

public interface DishService extends IService<Dish> {
}

serviceImpl类:

@Service
public class DishServiceImpl extends ServiceImpl<DishMapper, Dish> implements DishService {
}
4.2 Mybatis-plus分页查询组件的使用

要使用mybatis-plus为我们提供的插件,我们只需要写一个配置类,为mybatis-plus提供分页插件拦截器PaginationInnerInterceptor类,对mybatis-plus框架功能进行增强。

示例:

  1. 分页插件的配置
/*
* mybatis-plus分页插件的配置
* */
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor getMybatisPlusInterceptor(){
        MybatisPlusInterceptor mybatisPlusInterceptor=new MybatisPlusInterceptor();
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}
  1. 分页插件的使用

    步骤:1. 准备分页条件构造器 2.准备查询条件构造器 3.service.page(分页条件构造器,查询条件构造器)

        /*
        * 分类数据的分页查询
        * */
        @RequestMapping(value = "/backend/page/category/queryCategoryForPage.do")
        public R<Page<Category>> queryCategoryForPage(Integer page,Integer pageSize){
            //准备分页条件构造器
            Page<Category> pageInfo=new Page<>(page,pageSize);
            //进行排序条件的构造
            //排序条件: 先按type排序,type相同按sort排序
            LambdaQueryWrapper<Category> queryWrapper=new LambdaQueryWrapper<>();
            queryWrapper.orderByAsc(Category::getType,Category::getSort);
            //在进行完分页查询后,会把查询结果回调设置会pageInfo里面
            categoryService.page(pageInfo,queryWrapper);
            return R.success(pageInfo);
        }
    
4.3 Mybatis-plus 提供的公共字段自动填充功能的使用

公共字段的含义:

在数据库表与表中共同含有的字段,在Reggie项目中如createUser,createTime,updateUser,updateTime这些字段十分通用几乎每个表中都有,此时如果对于每个表的每次操作都考虑填充这些字段无疑十分繁琐,代码重复度也高,mybatis-plus可以通过简单配置MetaObjectHandler类就能够在每个sql语句到达数据库之前检查对象是否有这些字段并进行自动注入。

/*
* 自定义元数据对象处理器
* 完成公共字段自动填充功能
* 难点:如何动态的获得当前用户的id
* */
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        if(metaObject.hasSetter("createUser")){
            metaObject.setValue("createUser", UserIdContextHolder.getContextHolder());
        }
        if(metaObject.hasSetter("createTime")){
            metaObject.setValue("createTime", DateUtils.formatDateTime(new Date()));
        }
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        if(metaObject.hasSetter("updateUser")){
            metaObject.setValue("updateUser", UserIdContextHolder.getContextHolder());
        }
        if(metaObject.hasSetter("updateTime")){
            metaObject.setValue("updateTime", DateUtils.formatDateTime(new Date()));
        }
    }
}

4.4 编码技巧:借助ThreadLocal本地线程变量来储存信息

4.3当中其实还有一个亟待解决的问题:就是不论是当前是插入记录还是更新记录,即不论是createUser还是updateUser应该都是当前用户,那么如何获取当前用户的id呢?

因为之前是将id存入session中,自然的想从session当中取出值,但当前不是controller层无法取到session。

Tomact会为每一个http请求分配一个单独线程,因此我们可以在controller层或者filter这些能取到session中的id的时候把id储存到线程的本地线程变量中,在我们需要进行元数据对象填充的时候在从线程本地变量中取出id。

ThreadLocal的使用方法都是相近的。

/*
* 因为前端每次发出request请求,服务器都会为这次请求分配
* 一个新的线程,我们可以利用线程的ThreadLocal在请求到Controller的时候保存当前
* 用户的id到ThreadLocal中这样,我们就可以在MetaObjectHandler中动态获取到当前
* 用户的id
* */
public class UserIdContextHolder {
    private static final ThreadLocal<Long> CONTEXT_HOLDER=new ThreadLocal<>();

    public static void setContextHolder(Long id){
        CONTEXT_HOLDER.set(id);
    }
    public static Long getContextHolder(){
        return CONTEXT_HOLDER.get();
    }
    public static void remove(){
        CONTEXT_HOLDER.remove();
    }
}

使用案例:

Filter中的doFilter方法

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request=(HttpServletRequest)servletRequest;
        HttpServletResponse response=(HttpServletResponse)servletResponse;
        //业务代码
        //1:直接放行
        //2:如果已经登录直接放行
        if(request.getSession().getAttribute("employee")!=null){
            UserIdContextHolder.setContextHolder((Long) request.getSession().getAttribute("employee"));
            filterChain.doFilter(servletRequest,servletResponse);
            UserIdContextHolder.remove();
            return;
        }
        if(request.getSession().getAttribute("user")!=null){
            UserIdContextHolder.setContextHolder((Long) request.getSession().getAttribute("user"));
            filterChain.doFilter(servletRequest,servletResponse);
            UserIdContextHolder.remove();
            return;
        }
        //3:未登录,如果访问的是后台controller直接拦截
        //业务代码
        //4:未登录访问的是其它的资源,放行
        filterChain.doFilter(servletRequest,servletResponse);
    }

5. 全局异常处理器的使用与配置

请求到controller之后,调用service进行业务操作,一旦报错,一般我们会在controller中使用try-catch进行异常捕获,但是这个方法有一定的弊端,try-catch和业务代码混杂在一起,耦合度高,不易阅读。

我们可以配置全局异常处理器,通过SpringAop切面编程的技术,将全局异常处理器织入到所有被RestController或者Controller注解所注解的类。这样我们就可以把所有controller层中需要写的try-catch全部写到一个类中,代码更简洁,复用性更高。

案例:

@ControllerAdvice(annotations ={RestController.class, Controller.class})
@ResponseBody
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(SQLIntegrityConstraintViolationException.class)
    public R<String> SQLExceptionHandler(SQLIntegrityConstraintViolationException ex){
        log.error(ex.getMessage());
        String message=ex.getMessage();
        
        if(message.contains("Duplicate entry")){
            String[] split = message.split(" ");
            return R.error("字段:"+split[2]+"不能重复录入!");
        }
        return R.error("未知错误!");
    }
}

6. DTO数据传输对象的使用

在WEB项目中经常会遇到一种情况,前端传输的参数在后端controller层中原有的对象无法全部接收到前端传输的所有参数,因此我们可以创建一个原有对象对应的DTO对象继承原有对象,拓展新的属性以便接收前端传输的全部参数。

这一点在后端controller层返回值中也可以体现,Reggie项目中,controller的返回值封装成R对象中的data属性,即我们需要用一个对象封装前端想要的所有参数而返回,但有时候前端想要的所有数据可能后端已有的类都无法一个对象封装所有参数。因此我们可以在原有的类基础上继承一个子类拓展属性来满足要求。

示例:

public class DishDTO extends Dish {
    //封装了口味集合
    private List<DishFlavor> flavors;

    //菜品分类名称 后端数据库有的是categoryId但前端需要菜品分类的名称,原有的Dish对象不再能满足需求。在DishDTO中拓展categoryName属性满足条件.
    private String categoryName;

    //菜品的份数  前端传输菜品数据的时候会一并传输用户点这菜的份数,而后端的Dish对象无法封装菜品数目,于是在DishDTO中拓展copies属性以满足需要。
    private Integer copies;

    @Override
    public String toString() {
        return "DishDTO{" +
                "flavors=" + flavors +
                ", categoryName='" + categoryName + '\'' +
                ", copies=" + copies +
                '}';
    }
}

7. 文件的上传和下载

在WEB项目中文件上传和下载都是家常饭菜必不可少,而文件上传下载是很套路很模板化的知识点,没什么好说的,只要套用即可。

/*
* 实现文件上传和下载的Controller
* */
@RestController
public class CommonController {
    @Value("${file.upLoad.path}")
    private String FILE_UPLOAD_PATH;


    //返回值:文件上传成功时返回文件名称
    @RequestMapping("/common/upload")
    public R<String> fileUpLoadController(MultipartFile file){
        //文件上传时,接收到前端传输文件的file会默认在服务器生成一个临时存储文件
        //当这个方法执行完毕后,该临时存储文件会被销毁
        //所以我们需要将文件进行转存,转存到指定磁盘目录
        //为了防止文件名重复,用UUID生成文件名
        if(file.isEmpty()){
            return R.error("文件上传失败");
        }else{
            //1:如果FILE_UPLOAD_PATH文件夹没创建则需要创建
            File dic=new File(FILE_UPLOAD_PATH);
            if(!dic.exists()){
                dic.mkdirs();
            }
            String filename=GenerateUUID.getByFilename(file.getOriginalFilename());
            String realPath=FILE_UPLOAD_PATH+ filename;
            try {
                file.transferTo(new File(realPath));
            } catch (IOException e) {
                e.printStackTrace();
                return R.error("文件上传失败!");
            }
            return R.success(filename);
        }
    }

    @RequestMapping("/common/download")
    public void download(String name, HttpServletResponse response){
        //1:输入流和输出流
        //将磁盘中的文件以输入流的方式读进内存
        //将内存中的文件写入response当中
        String realPath=FILE_UPLOAD_PATH+name;
        InputStream inputStream=null;
        ServletOutputStream outputStream=null;
        response.setContentType("image/jpg");
        try {
            inputStream=new FileInputStream(new File(realPath));
            outputStream = response.getOutputStream();
            IOUtils.copy(inputStream,outputStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(inputStream!=null){
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(outputStream!=null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

8. Redis

8.1 RedisTemplate注意事项

redis是二进制安全的,在redis中存储的数据其实是经过序列化的字节流,而redis中数据类型仅仅代表数据的组织结构,并不是值其真实存储的数据。在实际项目中我们需要将redis作为缓存使用,将从数据库中查询出来的数据存储在redis中,而查询出来的数据一般都是对象,List集合,甚至需要将map存进redis当中,这时后我们就需要考虑要使用redis提供的啥数据类型进行存储?

我们可以统一用redis中的字符串类型来存储,将对象序列化为字节数组然后以字符串的形式保存在数据库当中。这样我们只需要配置RedisTemplate的value序列方式为JdkSerializationRedisSerializer,就可以将jave中的对象序列化为字符串,然后读出来的时候以同样的方式反序列化。

存在的问题:Redis支持很多语言,我们以JDK序列化器序列化的对象,别的语言写的服务器就无法正确的反序列化可能会导致乱码问题。如果真有这种需求可以考虑统一序列化为json格式的字符串,那么所有类型都能够访问。

    public R<String> updateDish(@RequestBody DishDTO dishDTO){
        //为了避免后台修改菜品的数据时,前端因为直接查缓存而看不到
        //所以直接从缓存当中去拿取数据
        String key="categoryId:"+dishDTO.getCategoryId();
        List<DishDTO> dishDTOList =(List<DishDTO>) redisTemplate.opsForValue().get(key);
        if(dishDTOList!=null){
            for(int i=0;i<dishDTOList.size();i++){
                DishDTO dto=dishDTOList.get(i);
                if(Objects.equals(dto.getId(), dishDTO.getId())){
                    dishDTOList.set(i,dishDTO);
                    break;
                }
            }
            redisTemplate.opsForValue().set(key,dishDTOList,1,TimeUnit.HOURS);
        }
        Dish dish=new Dish();
        BeanUtils.copyProperties(dishDTO,dish);
        //1: 根据id修改菜品表的数据信息
        dishService.updateById(dish);
        //2: 根据id修改口味表的信息
        //先清除该菜品下所有口味,再添加回去
        LambdaQueryWrapper<DishFlavor> queryWrapper=new LambdaQueryWrapper<>();
        queryWrapper.eq(DishFlavor::getDishId,dishDTO.getId());
        dishFlavorService.remove(queryWrapper);
        List<DishFlavor> flavors = dishDTO.getFlavors();
        for (DishFlavor flavor : flavors) {
            flavor.setDishId(dishDTO.getId());
        }
        dishFlavorService.saveBatch(flavors);
        return R.success("修改菜品信息成功!");
    }
8.2 Spring Cache简化开发

缓存一般都是用来解决读请求的,来降低落到mysql的访问压力,而当数据发生写操作时,根据实际

需求可能需要删除redis缓存或者同步缓存和数据库的数据。对于一些简单的逻辑我们完全可以用注解来实现,比如需要使用缓存的读请求,一般都是先看缓存中有没有,如果有直接从缓存中拿,没有去mysql中拿并回写到缓存中。spring cache框架支持用简单的注解来满足简单的使用缓存的需求,但若是有较为复杂的逻辑还需要自己来实现。

配置:

pom.xml

        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis --> // 导入redis的依赖关系
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.7.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-cache --> // 导入spring-cache的依赖包
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
            <version>2.7.0</version>
        </dependency>

application.yml

spring:
  redis:
    host: 192.168.233.141
    port: 6379
    password: root@123456
    database: 0
  cache:
    redis:
      time-to-live: 3600000 # redis中设置的key的默认过期时间,实际应用中为了避免缓存雪崩问题,设置的默认过期时间应该尽可能分散。

在启动类上开启注解缓存方式:

@ServletComponentScan
@SpringBootApplication
@EnableTransactionManagement
@EnableCaching  //开启spring-cache注解
public class ReggieApplication {

    public static void main(String[] args) {
        SpringApplication.run(ReggieApplication.class, args);
    }

}

spring cache常用注解


9. MySQL主从复制

9.1 为什么要有主从复制

mysql的主从复制的目的和redis主从复制的目的几乎都是一样的,为了解决单点故障问题,主mysql数据库挂了,从mysql数据库可以继续干活。可以进行读写分离,在并发量大的时候并且是读多写少的环境下,我们可以进行读写分离,让从mysql数据库为只读,主mysql数据库即可读也可以写,相当于分担了主msyql读的并发压力,系统可用性更高。

9.2 主从复制原理
9.4 mysql主从复制相关配置

主机master:

image-20220913100931981

需要注意的一点是:MySQL8新特性中,不能同时创建用户并给用户授权

要先创建用户,再给用户授权,否则会出语法错误。

让slave从机知道主机的二进制文件的位置在哪里。

从机slave:

  1. 配置从机的serverId注意:主机和从机的serverId必须不一样

  2. 重启mysql服务器

  3. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S01yEmQ3-1663037518369)(C:\Users\11425\AppData\Roaming\Typora\typora-user-images\image-20220913101527073.png)]

  4. 通过show slave status\G 查看从机的状态:注意这时需要着重查看redis的IO Thread和SQL Thread只有这两个线程都OK才Ok。

9.4 主从复制中可能出现的问题
  1. 可能出现因为MySQL8的身份验证方式是 :Caching_sha2_password 从而导致从机连接主机失败,这是因为Caching_sha2_password验证插件安全性更高需要配置RSA密码交互方式,否则会失败,如果不想配置,可以使用MySQL5.7 之前的版本的密码验证方式:mysql_native_password

    指令为:ALTER USER ‘root’@‘localhost’ IDENTIFIED WITH mysql_native_password BY ‘你的密码’;

  2. 从机可以通过show slave status\G 查看错误信息


10. Sharding-JDBC框架实现MySQL读写分离

pom.xml

        <!-- https://mvnrepository.com/artifact/org.apache.shardingsphere/sharding-jdbc-spring-boot-starter -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.1.1</version>
        </dependency>

application.yml 配置读写分离的相关参数,就可以实现读写分离了

spring:
 shardingsphere:
    datasource:
      names: master,slave
      master:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
          username: root
          password: 123456
          type: com.alibaba.druid.pool.DruidDataSource
          #Spring Boot 默认是不注入这些属性值的,需要自己绑定
          #druid 数据源专有配置
          initialSize: 5
          minIdle: 5
          maxActive: 20
          maxWait: 60000
          timeBetweenEvictionRunsMillis: 60000
          minEvictableIdleTimeMillis: 300000
          validationQuery: SELECT 1 FROM DUAL
          testWhileIdle: true
          testOnBorrow: false
          testOnReturn: false
          poolPreparedStatements: true
      slave:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://192.168.233.141:3306/reggie?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
          username: root
          password: Ai@15012706016
          type: com.alibaba.druid.pool.DruidDataSource
          #Spring Boot 默认是不注入这些属性值的,需要自己绑定
          #druid 数据源专有配置
          initialSize: 5
          minIdle: 5
          maxActive: 20
          maxWait: 60000
          timeBetweenEvictionRunsMillis: 60000
          minEvictableIdleTimeMillis: 300000
          validationQuery: SELECT 1 FROM DUAL
          testWhileIdle: true
          testOnBorrow: false
          testOnReturn: false
          poolPreparedStatements: true
    masterslave: # 主从复制的配置
      # 负载均衡的配置:配置为轮询
      load-balance-algorithm-type: round_robin
      # 最终暴露的数据源名称
      name: datasource
      # 从数据库名称列表,用','号隔开
      slave-data-source-names: slave
      props:
        sql:
          show: true # 开启在控制台显示sql,默认是false
      master-data-source-name: master

二:Reggie项目感言

Reegie外卖项目更多的是CRUD,调用API和库,总体上功能简单,没有什么难点,也没有高并发的场景可以供调优来实践,总体上还是比较简单的。

     maxActive: 20
      maxWait: 60000
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true
masterslave: # 主从复制的配置
  # 负载均衡的配置:配置为轮询
  load-balance-algorithm-type: round_robin
  # 最终暴露的数据源名称
  name: datasource
  # 从数据库名称列表,用','号隔开
  slave-data-source-names: slave
  props:
    sql:
      show: true # 开启在控制台显示sql,默认是false
  master-data-source-name: master



## 二:Reggie项目感言

Reegie外卖项目更多的是CRUD,调用API和库,总体上功能简单,没有什么难点,也没有高并发的场景可以供调优来实践,总体上还是比较简单的。

但还是能够学到很多新技术,新框架的使用,确实大大简化了开发,提高了效率,但写完代码后应该还需要再重构一次。
  • 25
    点赞
  • 259
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
### 回答1: 瑞吉外卖是一个基于Java开发的项目实战,适用于在线外卖订餐系统。该项目提供了用户注册、登录、浏览餐厅、查看菜单、下订单等功能。 首先,我们需要在网盘上下载瑞吉外卖项目源代码文件。通过提供的下载链接,我们可以将项目源代码文件下载到本地。下载完成后,我们可以将文件解压缩,并使用Java开发工具(如Eclipse或IntelliJ IDEA)导入项目。 接下来,我们需要安装项目所需的Java开发环境。确保已经安装了JDK(Java Development Kit)和Maven(项目构建工具)。这样可以保证项目能够正常编译和运行。 在导入项目后,我们可以查看项目的目录结构。主要包括源代码、配置文件和静态资源文件等。在源代码文件夹中,我们可以找到各种Java类文件,包括控制器、实体类、服务类等。配置文件夹中包含项目的配置文件,用于配置数据库连接、日志记录等。静态资源文件夹中包含了项目所需的各种图片、样式表和JavaScript文件等。 在开始开发之前,我们需要先配置数据库。将提供的SQL脚本文件导入到MySQL数据库中,并在项目配置文件中修改数据库连接相关的配置信息。 接下来,我们可以根据需求对项目进行开发和定制化。例如,我们可以根据需要添加更多的功能模块,如优惠券管理、配送员管理等。我们也可以根据需求修改前端页面的样式和布局,以满足用户的需求。 开发完成后,我们可以使用Maven将项目打包成可执行的WAR文件。将WAR文件上传至服务器,并部署在Tomcat等Java Web服务器上。通过访问服务器的IP地址和端口号,我们就可以在浏览器中访问瑞吉外卖系统了。 总之,下载并实战瑞吉外卖项目需要下载源代码文件,并在Java开发工具中导入项目。然后,我们可以根据需求进行开发和定制化,并最终将项目打包部署在服务器上。最后,我们可以通过浏览器访问项目,体验瑞吉外卖系统的功能。 ### 回答2: 瑞吉外卖是一个基于Java语言开发的项目实战,项目的主要目标是实现一个在线外卖订餐系统。用户可以通过网页或手机应用程序浏览餐厅菜单、下订单、查看订单状态等功能。 该项目的开发环境主要包括Java SE、Java EE、Spring框架和MySQL数据库。其中,Java SE用于实现基本的语言特性和数据处理操作,Java EE用于构建Web应用程序,Spring框架用于实现系统的MVC架构,MySQL数据库用于存储用户信息、菜品信息和订单数据等。 项目的实施步骤如下: 1. 需求分析:首先,根据用户的需求分析,确定项目的基本功能和需求。 2. 系统设计:基于需求分析的结果,进行系统设计,包括数据库设计、界面设计和系统架构设计等。 3. 环境搭建:安装配置Java开发环境,包括JDK、开发工具(如Eclipse或IntelliJ IDEA)、Web服务器(如Tomcat)和数据库管理系统(MySQL)。 4. 数据库建模:创建数据库表结构,定义各个表之间的关系。 5. 编码实现:根据系统设计的结果,进行编码实现,包括前端界面的开发和后端功能的开发。 6. 软件测试:对已实现的功能进行测试,包括单元测试、集成测试和系统测试等,保证系统的稳定性和可靠性。 7. 部署上线:将项目部署到服务器上,使用户可以通过网络访问系统。 8. 运维和优化:监控系统运行情况,对性能进行优化和改进。 最后,用户可以通过网盘下载瑞吉外卖的源代码和相关文档,以便学习和参考。项目实战瑞吉外卖的开发过程将帮助开发者熟悉Java开发技术,并理解实际项目的需求分析、系统设计和开发实施等流程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值