尚硅谷-尚庭公寓知识点


尚庭公寓知识点

1、转换器(Converter)

场景:当数据库中存在多个状态字段,且都使用了枚举类进行了封装,此时就需要转换器(p99集)

在讲解之前我们需要知道请求参数从发起到响应的流程:

  • 请求流程
    在这里插入图片描述

说明

  • SpringMVC中的WebDataBinder组件负责将HTTP的请求参数绑定到Controller方法的参数,并实现参数类型的转换。

  • Mybatis中的TypeHandler用于处理Java中的实体对象与数据库之间的数据类型转换。

  • 响应流程
    在这里插入图片描述

    说明

    • SpringMVC中的HTTPMessageConverter组件负责将Controller方法的返回值(Java对象)转换为HTTP响应体中的JSON字符串,或者将请求体中的JSON字符串转换为Controller方法中的参数(Java对象),例如下一个接口保存或更新标签信息
      在这里插入图片描述

WebDataBinder枚举类型转换

WebDataBinder依赖于Converter实现类型转换,若Controller方法声明的@RequestParam参数的类型不是StringWebDataBinder就会自动进行数据类型转换。SpringMVC提供了常用类型的转换器,例如StringIntegerStringDateStringBoolean等等,其中也包括String到枚举类型,但是String到枚举类型的默认转换规则是根据实例名称(“APARTMENT”)转换为枚举对象实例(ItemType.APARTMENT)。若想实现code属性到枚举对象实例的转换,需要自定义Converter,代码如下,具体内容可参考官方文档

  • web-admin模块自定义com.atguigu.lease.web.admin.custom.converter.StringToItemTypeConverter
@Component
public class StringToItemTypeConverter implements Converter<String, ItemType> {
    @Override
    public ItemType convert(String code) {

        for (ItemType value : ItemType.values()) {
            if (value.getCode().equals(Integer.valueOf(code))) {
                return value;
            }
        }
        throw new IllegalArgumentException("code非法");
    }
}

注册上述的StringToItemTypeConverter,在web-admin模块创建com.atguigu.lease.web.admin.custom.config.WebMvcConfiguration
内容如下:

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
  @Autowired
  private StringToItemTypeConverter stringToItemTypeConverter;
  
  /*注册stringToItemTypeConverter*/
  @Override
  public void addFormatters(FormatterRegistry registry) {
      registry.addConverter(stringToItemTypeConverter);
  }
}

addFormatters的工作原理:

Client DispatcherServlet HandlerMethod FormatterRegistry DateFormatter 发送请求 /user?birth=2023/10/01 解析请求参数 查找匹配的Formatter 返回DateFormatter 转换字符串为Date对象 返回Date实例 执行控制器方法 返回响应 Client DispatcherServlet HandlerMethod FormatterRegistry DateFormatter

但是我们有很多的枚举类型都需要考虑类型转换这个问题,按照上述思路,我们需要为每个枚举类型都定义一个Converter,并且每个Converter的转换逻辑都完全相同,针对这种情况,我们使用ConverterFactory接口更为合适,这个接口可以将同一个转换逻辑应用到一个接口的所有实现类,因此我们可以定义一个BaseEnum接口,然后另所有的枚举类都实现该接口,然后就可以自定义ConverterFactory,集中编写各枚举类的转换逻辑了。具体实现如下:

  • model模块定义com.atguigu.lease.model.enums.BaseEnum接口

    public interface BaseEnum {
        Integer getCode();
        String getName();
    }
    
  • 令所有com.atguigu.lease.model.enums包下的枚举类都实现BaseEnun接口

  • web-admin模块自定义com.atguigu.lease.web.admin.custom.converter.StringToBaseEnumConverterFactory

/**
 * 统一将String转换为BaseEnum
 * 执行逻辑为:当SpringMvc要进行String -> BaseEnum(包括子类)型转换时就会执行Converter()方法
 */
@Component
public class StringToBaseEnumConverterFactory implements ConverterFactory<String, BaseEnum> {

  /**
   * 当状态类的枚举过多时可以使用converterFactory(转换器工厂)进行统一转换(前提是原数据类型相同【String】,转换的数据类型都有统一的继承【BaseEnum】)
   * @param targetType 【转化的类型】
   * @return 返回值可以直接使用new Converter...进行设置
   * @param <T> 表示继承于BaseEnum的所有对象
   */
  @Override
  public <T extends BaseEnum> Converter<String, T> getConverter(Class<T> targetType) {
    /*Converter(转换器)*/
    return new Converter<String, T>() {
      @Override
      public T convert(String source) {
        /**
         * targetType.getEnumConstants:遍历枚举常量
         */
        for (T enumConstant : targetType.getEnumConstants()) {
          /*判断code是否与参数source相等,相等代表这个枚举常量是目标则返回*/
          if (enumConstant.getCode().equals(Integer.parseInt(source))) {
            return enumConstant;
          }
        }
        throw new RuntimeException("code:" + source + "code非法");
      }
    };
  }
}
  • 注册上述的ConverterFactory,在web-admin模块创建com.atguigu.lease.web.admin.custom.config.WebMvcConfiguration,内容如下:
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
    @Autowired
    private StringToBaseEnumConverterFactory stringToBaseEnumConverterFactory;
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverterFactory(this.stringToBaseEnumConverterFactory);
    }
}

此时请求参数已经可以转化为对应枚举对象了,但是但是还需要配置MyBatisPlus映射到数据库的类型转化

  • ypeHandler枚举类型转换**

    Mybatis预置的TypeHandler可以处理常用的数据类型转换,例如StringIntegerDate等等,其中也包含枚举类型,但是枚举类型的默认转换规则是枚举对象实例(ItemType.APARTMENT)和实例名称(“APARTMENT”)相互映射。若想实现code属性到枚举对象实例的相互映射,需要自定义TypeHandler

    不过MybatisPlus提供了一个通用的处理枚举类型的TypeHandler。其使用十分简单,只需在ItemType枚举类的code属性上增加一个注解@EnumValue,Mybatis-Plus便可完成从ItemType对象到code属性之间的相互映射,具体配置如下。

public enum ItemType {

    APARTMENT(1, "公寓"),
    ROOM(2, "房间");

    @EnumValue
    private Integer code;
    private String name;

    ItemType(Integer code, String name) {
        this.code = code;
        this.name = name;
    }
}
  • HTTPMessageConverter枚举类型转换

    HttpMessageConverter依赖于Json序列化框架(默认使用Jackson)。其对枚举类型的默认处理规则也是枚举对象实例(ItemType.APARTMENT)和实例名称(“APARTMENT”)相互映射。不过其提供了一个注解@JsonValue,同样只需在ItemType枚举类的code属性上增加一个注解@JsonValue,Jackson便可完成从ItemType对象到code属性之间的互相映射。具体配置如下,详细信息可参考Jackson官方文档

@Getter
public enum ItemType {

    APARTMENT(1, "公寓"),
    ROOM(2, "房间");

    @EnumValue
      @JsonValue
    private Integer code;
    private String name;

    ItemType(Integer code, String name) {
        this.code = code;
        this.name = name;
    }
}

2、全局异常

官网地址:https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-exceptionhandler.html
可以把他理解为对项目异常的拦截器,每当报某一个Exception时便可以自定以操作(例如:直接响应Result.fail)

使用创建一个标记注解@ControllerAdvice

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    /*将返回值序列化为Http响应体*/
    @ResponseBody
    public Result exceptionHanler(Exception e) {
        e.printStackTrace();
        return Result.fail();
    }

    @ExceptionHandler(LeaseException.class)
    @ResponseBody
    public Result LeaseExceptionHandler(LeaseException e) {
        return Result.handler(e.getMessage(), e.getCode());
    }
}

3、定时任务

当想要完成一个根据时间动态的去执行某一个逻辑,这时就可以使用springboot中提供的定时任务

1. 核心步骤

(1) 启用定时任务

在主配置类或任意 @Configuration 类上添加 @EnableScheduling

@SpringBootApplication
@EnableScheduling // 启用定时任务支持
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class, args);
    }
}
(2) 创建定时任务

在 Bean 的方法上使用 @Scheduled 注解:

@Component
public class MyTaskScheduler {

    // 固定频率执行(每 5 秒)
    @Scheduled(fixedRate = 5000) // 单位:毫秒
    public void task1() {
        System.out.println("固定频率任务执行: " + LocalDateTime.now());
    }

    // 固定延迟执行(上次任务结束后延迟 3 秒)
    @Scheduled(fixedDelay = 3000)
    public void task2() {
        System.out.println("固定延迟任务执行: " + LocalDateTime.now());
    }

    // 使用 Cron 表达式(每天 12:00 执行)
    @Scheduled(cron = "0 0 12 * * ?") 
    public void task3() {
        System.out.println("Cron 任务执行: " + LocalDateTime.now());
    }
}

2. @Scheduled 参数详解

参数说明示例
fixedRate固定频率(毫秒),上次开始时间后间隔执行fixedRate = 5000
fixedDelay固定延迟(毫秒),上次结束时间后间隔执行fixedDelay = 3000
initialDelay首次延迟(毫秒),需配合 fixedRate/fixedDelay 使用initialDelay = 10000
cronCron 表达式(支持秒级)cron = "0 15 10 * * ?"
zone时区(默认为服务器时区)zone = "Asia/Shanghai"

3. Cron 表达式语法

Spring Boot 支持 6位 Cron 表达式(包含秒):

秒  分  时  日  月  周

常用示例:

  • 0 * * * * ?:每分钟的 0 秒执行
  • 0 0 10 * * ?:每天 10:00 执行
  • 0 0/5 14 * * ?:每天 14:00-14:55,每 5 分钟执行
  • 0 15 10 ? * MON-FRI:周一至周五 10:15 执行

4. 配置线程池(避免阻塞)

默认所有任务在单线程中执行。若需并发,需自定义线程池:

@Configuration
public class SchedulerConfig implements SchedulingConfigurer {

    @Override
    public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(5); // 线程池大小
        taskScheduler.setThreadNamePrefix("my-scheduler-");
        taskScheduler.initialize();
        taskRegistrar.setTaskScheduler(taskScheduler);
    }
}

5. 动态控制任务(高级用法)

通过 ScheduledTaskRegistrar 动态注册/取消任务:

@Service
public class DynamicTaskService {

    @Autowired
    private ScheduledTaskRegistrar taskRegistrar;
    
    private ScheduledFuture<?> future;

    // 启动任务
    public void startTask() {
        future = taskRegistrar.getScheduler().schedule(
            () -> System.out.println("动态任务执行: " + LocalDateTime.now()),
            new CronTrigger("0/10 * * * * ?") // 每 10 秒
        );
    }

    // 停止任务
    public void stopTask() {
        if (future != null) {
            future.cancel(true); // 取消任务
        }
    }
}

6. 注意事项

  1. 避免长时间阻塞:任务执行时间 > 间隔时间会导致任务堆积(需优化或异步处理)。
  2. 单线程问题:默认单线程顺序执行,耗时任务需配置线程池。
  3. 分布式环境:集群中多个节点会同时执行任务(需额外方案如分布式锁或 Quartz 集成)。

4、MyBatisPlus的修改策略

使用场景:
当使用MyBatisPlus提供的update的APi时会发现,
他会做一个判断当某一个字段的值为null时便不会更新此字段,
而若是想要更新就需要去配置MyBatisPlus中的修改策略(update-strategy)

在 MyBatis-Plus 中,字段的更新策略(update-strategy)主要通过 @TableField 注解的 updateStrategy 属性或全局配置来控制。以下是详细说明和配置方法:


1. 更新策略类型(FieldStrategy 枚举)

策略说明
IGNORED忽略判断:无论字段值是什么,都会更新到数据库(即使为 null)
NOT_NULL非 NULL 判断:字段值不为 null 时才更新(默认值)
NOT_EMPTY非空判断:字段值不为 null 且不为空(如字符串长度>0)才更新
NEVER永不更新:该字段永远不会被更新到数据库
DEFAULT跟随全局配置

2. 配置方式

(1) 字段级配置(注解方式:局部配置)

在实体类字段上使用 @TableField 注解:

public class User {
    @TableField(updateStrategy = FieldStrategy.IGNORED)
    private String email;  // 总是更新
    
    @TableField(updateStrategy = FieldStrategy.NOT_EMPTY)
    private String nickname;  // 非空时才更新
    
    @TableField(updateStrategy = FieldStrategy.NEVER)
    private LocalDateTime createTime;  // 永不更新
}
(2) 全局配置(application.yml)
mybatis-plus:
  global-config:
    db-config:
      update-strategy: not_null  # 全局默认策略
      # 可选值: ignored, not_null, not_empty, never, default

优先级:字段注解 > 全局配置


3. 使用场景示例

场景 1:允许更新为 null 值
@TableField(updateStrategy = FieldStrategy.IGNORED)
private String phone;  // 可更新为 null
场景 2:空值不覆盖数据库原有值
@TableField(updateStrategy = FieldStrategy.NOT_NULL)
private Integer age;  // 若 age=null 则跳过更新
场景 3:创建时间永不更新
@TableField(updateStrategy = FieldStrategy.NEVER)
private LocalDateTime createTime;

4. 更新操作行为说明

  • 使用 updateById()
User user = new User();
user.setId(1L);
user.setEmail(null);  // 策略=IGNORED → 更新为 null
user.setNickname(""); // 策略=NOT_EMPTY → 跳过更新
userMapper.updateById(user);

生成 SQL:

UPDATE user SET email = null WHERE id = 1;
  • 使用 updateWrapper
    通过 Wrapper 更新的字段不受策略影响,会直接更新:
new UpdateWrapper<User>()
    .set("nickname", null)  // 直接更新为 null
    .eq("id", 1);

5. 动态更新策略技巧

通过条件构造器实现动态更新:

public void updateUserConditional(Long id, String name, String email) {
    UpdateWrapper<User> wrapper = new UpdateWrapper<User>().eq("id", id);
    
    if (name != null) {
        wrapper.set("name", name);  // 只有非 null 才更新 name
    }
    wrapper.set("email", email);    // 总是更新 email
    
    userMapper.update(null, wrapper);
}

6. 注意事项

  1. 策略仅影响非 null 字段
  • 如果字段在更新时未被设置(值为 null),则根据策略决定是否更新
  • 如果显式设置了值(即使为 null),则受 IGNORED/NEVER 等策略控制
  1. 插入策略 vs 更新策略
  • insertStrategy 控制插入行为(通过 @TableField(insertStrategy=...) 配置)
  • updateStrategy 仅控制更新行为
  1. 全局默认值变更
  • MyBatis-Plus 3.x 开始全局默认策略从 NOT_NULL 改为 DEFAULT(即不处理)
  • 建议显式配置全局策略

5、图形化验证码 EasyCaptcha(Gitee)

EasyCaptcha此项目使用的是在Gitee开源开源项目,
他可以自动生成一个图形化验证码,其支持多种类型的验证码,例如gif、中文、算术等。

使用方式:结合redis可以做一个图形化验证码(指定时间过期)的逻辑,

1、导入依赖

<!--captcha-->
<dependency>
    <groupId>com.github.whvcse</groupId>
    <artifactId>easy-captcha</artifactId>
</dependency>
<!--redis-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置redis

spring:
  data:
    redis:
#      主机地址
      host: <hostname>
#      端口
      port: <port>
#      库
      database: 0

2、使用示例

@Override
public CaptchaVo getCaptcha() {
    /*创建随机的一个验证码图像(二进制)*/
    SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 4);
    /*图像对应的值,并将值转化为小写(实现不区分大小写)*/
    String code = specCaptcha.text().toLowerCase();
    /*为图像拼接标识唯一id,方便存储到redis中*/
    String key = RedisConstant.ADMIN_LOGIN_PREFIX + UUID.randomUUID();
    /*存到redis中,并设置超时时间*/
    stringRedisTemplate.opsForValue()
            .set(key, code, RedisConstant.ADMIN_LOGIN_CAPTCHA_TTL_SEC, TimeUnit.SECONDS);
    /*.toBase64()返回的值,前端可以直接使用赋值给img标签中src属性,自动显示一个验证码*/
    String image = specCaptcha.toBase64();
    return new CaptchaVo(image, key);
}
/*配置redis常量*/
public class RedisConstant {
  public static final String ADMIN_LOGIN_PREFIX = "admin:login:";
  public static final Integer ADMIN_LOGIN_CAPTCHA_TTL_SEC = 60;
}

6、线程本地(ThreadLocal)储存个人信息

当JWT中存储着个人信息(id、userName),
此时想要查询详细信息便可以:解析JWT获取id->根据id查询信息->返回

此方案可以完成业务,但是在拦截器中我们也会对JWT进行解析判断是否合法,会重复的解析JWT,此时便可以使用本地线程(ThreadLocal)储存个人信息

实现步骤:

  • 创建个人信息的对象
public class LoginUser {
  private String userName;
  private Long userId;
}

  • 请求的数据结构
    按理说,前端若想获取当前登录用户的个人信息,需要传递当前用户的id到后端进行查询。但是由于请求中携带的JWT中就包含了当前登录用户的id,故请求个人信息时,就无需再传递id

  • 编写ThreadLocal工具类

    理论上我们可以在Controller方法中,使用@RequestHeader获取JWT,然后在进行解析,如下

    @Operation(summary = "获取登陆用户个人信息")
    @GetMapping("info")
    public Result<SystemUserInfoVo> info(@RequestHeader("access-token") String token) {
        Claims claims = JwtUtil.parseToken(token);
        Long userId = claims.get("userId", Long.class);
        SystemUserInfoVo userInfo = service.getLoginUserInfo(userId);
        return Result.ok(userInfo);
    }
    

    上述代码的逻辑没有任何问题,但是这样做,JWT会被重复解析两次(一次在拦截器中,一次在该方法中)。为避免重复解析,通常会在拦截器将Token解析完毕后,将结果保存至ThreadLocal中,这样一来,我们便可以在整个请求的处理流程中进行访问了。

ThreadLocal概述

ThreadLocal的主要作用是为每个使用它的线程提供一个独立的变量副本,使每个线程都可以操作自己的变量,而不会互相干扰,其用法如下图所示。

在这里插入图片描述

创建本地线程的工具类:

public class LoginUserHolder {
  /*创建本地线程*/
  public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>();

  /*获取本地线程资源*/
  public static LoginUser getLoginUser() {
    return threadLocal.get();
  }

  /*添加本地线程的资源*/
  public static void setThreadLocal(LoginUser loginUser) {
    threadLocal.set(loginUser);
  }

  /*清除线程占用的资源*/
  public static void clear() {
    threadLocal.remove();
  }
}
  • 修改AuthenticationInterceptor拦截器
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {

  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    /*获取jwt*/
    String accessToken = request.getHeader("access-token");
    /*解析jwt*/
    Claims claims = JwtUtil.parseJwt(accessToken);
    /*获取个人信息对象*/
    LoginUser loginUser = new LoginUser(claims.get("userName", String.class), claims.get("userId", Long.class));
    /*添加本地线程资源*/
    LoginUserHolder.setThreadLocal(loginUser);
    return true;
  }

  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    /*释放线程资源*/
    LoginUserHolder.clear();
  }
}

调用本地线程的属性:

Long userId = LoginUserHolder.getLoginUser().getUserId();
String userName = LoginUserHolder.getLoginUser().getUserName();

7、Mybatis-Plus分页插件注意事项

使用Mybatis-Plus的分页插件进行分页查询时,如果结果需要使用<collection>
进行映射,只能使用 嵌套查询(Nested Select for Collection),而不能使用
嵌套结果映射(Nested Results for Collection)
嵌套查询
嵌套结果映射**是Collection映射的两种方式,下面通过一个案例进行介绍
例如有room_infograph_info两张表,其关系为一对多,如下

知识点

  • xml文件<>的转义
    由于xml文件中的<>是特殊符号,需要转义处理。
原符号转义符号
<&lt;
>&gt;

在这里插入图片描述

现需要查询房间列表及其图片信息,期望返回的结果如下

[
    {
        "id": 1,
        "number": 201,
        "rent": 2000,
        "graphList": [
            {
                "id": 1,
                "url": "http://",
                "roomId": 1
            },
            {
                "id": 2,
                "url": "http://",
                "roomId": 1
            }
        ]
    },
    {
        "id": 2,
        "number": 202,
        "rent": 3000,
        "graphList": [
            {
                "id": 3,
                "url": "http://",
                "roomId": 2
            },
            {
                "id": 4,
                "url": "http://",
                "roomId": 2
            }
        ]
    }
]

为得到上述结果,可使用以下两种方式

  • 嵌套结果映射
<select id="selectRoomPage" resultMap="RoomPageMap">
    select ri.id room_id,
           ri.number,
           ri.rent,
           gi.id graph_id,
           gi.url,
           gi.room_id
    from room_info ri
       left join graph_info gi on ri.id=gi.room_id
</select>
    
<resultMap id="RoomPageMap" type="RoomInfoVo" autoMapping="true">
    <id column="room_id" property="id"/>
    <collection property="graphInfoList" ofType="GraphInfo" autoMapping="true">
        <id column="graph_id" property="id"/>
    </collection>
</resultMap>

这种方式的执行原理如下图所示

在这里插入图片描述

  • 嵌套查询
<select id="selectRoomPage" resultMap="RoomPageMap">
    select id,
           number,
           rent
    from room_info
</select>
    
<resultMap id="RoomPageMap" type="RoomInfoVo" autoMapping="true">
    <id column="id" property="id"/>
    <collection property="graphInfoList" ofType="GraphInfo" select="selectGraphByRoomId" 				 	column="id"/>
</resultMap>
    
<select id="selectGraphByRoomId" resultType="GraphInfo">
    select id,
           url,
           room_id
    from graph_info
    where room_id = #{id}
</select>

这种方法使用两个独立的查询语句来获取一对多关系的数据。首先,
Mybatis会执行主查询来获取room_info列表,然后对于每个room_info
Mybatis都会执行一次子查询来获取其对应的graph_info

在这里插入图片描述
若现在使用MybatisPlus的分页插件进行分页查询,
假如查询的内容是第1页,每页2条记录,
则上述两种方式的查询结果分别是

  • 嵌套结果映射
    在这里插入图片描述
  • 嵌套查询
    在这里插入图片描述
    显然嵌套结果映射的分页逻辑是存在问题的。

8、异步调用(@Async)

当执行一个业务是只需要后台内部完成的操作,不需要响应给前端(列如保存浏览记录),
便可以进行异步调用从而提升用户体验

异步调用本质

  1. 非阻塞执行:主线程提交任务后立即返回,不等待任务完成
  2. 线程池管理:任务由线程池中的工作线程执行
  3. 结果处理:通过 Future 或回调机制获取执行结果

实现原理

主线程 线程池 Worker线程 提交异步任务 立即返回Future/void 分配任务 执行任务 返回结果(可选) 主线程 线程池 Worker线程

使用示例

启动异步调用
@SpringBootApplication
/*支持异步调用*/
@EnableAsync
public class MainApp {
    public static void main(String[] args) {
        SpringApplication.run(MainApp.class, args);
    }
}
声明异步处理的方法(@Async)
@Override
/*异步调用*/
@Async
public void saveHistory(Long userId, Long roomId) {
//  ...
}

此时当主线程调用此方法时便不会等待此方法执行完再返回而是直接返回。


本文档和代码以上传gitee上:https://gitee.com/banhuayue/shangting-apartment.git
大家可以直接克隆。
本文档参考《尚硅谷-尚庭公寓》

### 关于硅谷 Java 项目——尚庭公寓 #### 项目背景与目标 尚庭公寓是一个基于 Java 技术栈开发的物业管理系统,旨在通过现代化的技术手段提升物业管理工作效率。该项目涵盖了从前端到后端的整体架构设计,适合初学者和中级开发者深入学习 Java Web 开发的核心技能[^1]。 --- #### 项目核心模块解析 以下是尚庭公寓项目的几个主要部分及其功能描述: ##### 1. **项目业务概述** - 尚庭公寓的主要业务场景包括租客管理、房屋租赁管理、费用结算以及管理员权限控制等。 - 这些功能的设计围绕实际生活中的物业管理需求展开,提供了完整的解决方案。 ##### 2. **移动端支持** - 提供轻量级的移动应用界面,方便住户随时查看账单、提交维修申请等功能。 - 使用 HTML5 和 Vue.js 构建响应式页面,确保跨平台兼容性。 ##### 3. **后台管理系统** - 后台采用 Spring Boot 框架构建,集成了 MyBatis-Plus 作为持久层框架。 - 实现了用户认证、角色授权、数据增删改查等一系列基础功能。 ##### 4. **核心业务流程** - 用户注册 -> 房屋信息录入 -> 租赁合同签订 -> 收费通知 -> 维修工单处理。 - 流程清晰明了,便于维护和扩展。 ##### 5. **技术选型** - 前端:Vue.js / Element UI - 后端:Spring Boot / MyBatis-Plus - 数据库:MySQL - 接口文档工具:Knife4j 这些技术的选择充分考虑了性能、易用性和可维护性。 --- #### 初始化准备工作详解 为了快速上手尚庭公寓项目,可以按照以下步骤完成环境搭建: ##### 1. **导入数据库** - 下载官方提供的 SQL 文件并执行脚本初始化表结构和测试数据。 ##### 2. **创建工程** - 使用 IntelliJ IDEA 或 Eclipse 新建 Maven 工程,并引入必要的依赖项。 ##### 3. **Spring Boot 初始配置** - 编辑 `pom.xml` 添加所需插件和支持版本号。 - 配置 `application.yml` 文件指定数据库连接参数和其他全局设置。 ##### 4. **MyBatis Plus 集成** - 修改 `pom.xml` 中加入 MyBatis-Plus 的相关依赖。 - 在 `application.yml` 设置扫描路径及分页插件选项。 ##### 5. **代码生成器** - 利用 MyBatis-Plus 自带的代码生成器自动生成实体类、Mapper 及 Service 层接口。 --- #### 登录功能实现案例分析 以教师登录为例,展示如何调用服务方法获取特定用户的详细信息: ```java @Override public Teacher getTeacherById(Long userId) { Teacher teacher = baseMapper.selectById(userId); return teacher; } ``` 上述逻辑展示了通过 ID 查询对应记录的过程,其中 `baseMapper` 是由 MyBatis 自动生成的基础 Mapper 对象[^2]。 --- #### GitHub 存储库地址 如果希望进一步研究源码或者参与贡献,可以通过以下链接访问公开仓库: [GitHub 地址](https://github.com/sungongtai/lease)[^3] 此存储库包含了完整的项目文件夹结构、README 文档以及其他辅助资源,非常适合自学或团队协作实践[^3]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值