学习: 尚硅谷Java项目之尚庭公寓(2)

三、 后台管理系统后端开发

3.租赁管理

看房预约管理

根据条件分页查询预约信息

Alt+Enter,实现方法

创建方法、Generate statement

XML 复制代码
    <resultMap id="AppointmentVoMap" type="org.example.lease.web.admin.vo.appointment.AppointmentVo" autoMapping="true">
        <id property="id" column="id"/>
        <association property="apartmentInfo" javaType="org.example.lease.model.entity.ApartmentInfo"
                     autoMapping="true">
            <id property="id" column="apartment_id"/>
            <result property="name" column="apartment_name"/>
        </association>
    </resultMap>
    <select id="pageAppointmentByQuery" resultMap="AppointmentVoMap">
        select va.id,
               va.user_id,
               va.name,
               va.phone,
               va.appointment_time,
               va.additional_info,
               va.appointment_status,
               ai.id   apartment_id,
               ai.name apartment_name,
               ai.district_id,
               ai.district_name,
               ai.city_id,
               ai.city_name,
               ai.province_id,
               ai.province_name
        from view_appointment va
                 left join
             apartment_info ai
             on va.apartment_id = ai.id and ai.is_deleted = 0
        <where>
            va.is_deleted = 0
            <if test="queryVo.provinceId != null">
                and ai.province_id = #{queryVo.provinceId}
            </if>
            <if test="queryVo.cityId != null">
                and ai.city_id = #{queryVo.cityId}
            </if>
            <if test="queryVo.districtId != null">
                and ai.district_id = #{queryVo.districtId}
            </if>
            <if test="queryVo.apartmentId != null">
                and va.apartment_id = #{queryVo.apartmentId}
            </if>
            <if test="queryVo.name != null and queryVo.name != ''">
                and va.name like concat('%',#{queryVo.name},'%')
            </if>
            <if test="queryVo.phone != null and queryVo.phone != ''">
                and va.phone like concat('%',#{queryVo.phone},'%')
            </if>
        </where>
    </select>

成功

设置序列化后的时间字符串

格式

XML 复制代码
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")

时区

XML 复制代码
@JsonFormat(timezone = "GMT+8")
XML 复制代码
spring:
  jackson:
    time-zone: GMT+8
根据ID更新预约状态

成功

租约管理

保存获更新租约信息
根据条件分页查询租约列表

Alt+Enter,实现方法

创建方法、Generate statement

XML 复制代码
   <resultMap id="agreementVoMap" type="org.example.lease.web.admin.vo.agreement.AgreementVo" autoMapping="true">
        <id property="id" column="id"/>
        <association property="apartmentInfo" javaType="org.example.lease.model.entity.ApartmentInfo"
                     autoMapping="true">
            <id property="id" column="apartment_id"/>
            <result property="name" column="apartment_name"/>
        </association>
        <association property="roomInfo" javaType="org.example.lease.model.entity.RoomInfo" autoMapping="true">
            <id property="id" column="room_id"/>
        </association>
        <association property="paymentType" javaType="org.example.lease.model.entity.PaymentType" autoMapping="true">
            <id property="id" column="payment_type_id"/>
            <result property="name" column="payment_type_name"/>
        </association>
        <association property="leaseTerm" javaType="org.example.lease.model.entity.LeaseTerm" autoMapping="true">
            <id property="id" column="lease_term_id"/>
        </association>
    </resultMap>

    <select id="pageAgreementByQuery" resultMap="agreementVoMap">
        select la.id,
               la.phone,
               la.name,
               la.identification_number,
               la.lease_start_date,
               la.lease_end_date,
               la.rent,
               la.deposit,
               la.status,
               la.source_type,
               la.additional_info,
               ai.id   apartment_id,
               ai.name apartment_name,
               ai.district_id,
               ai.district_name,
               ai.city_id,
               ai.city_name,
               ai.province_id,
               ai.province_name,
               ri.id   room_id,
               ri.room_number,
               pt.id   payment_type_id,
               pt.name payment_type_name,
               pt.pay_month_count,
               lt.id   lease_term_id,
               lt.month_count,
               lt.unit
        from lease_agreement la
                 left join
             apartment_info ai
             on la.apartment_id = ai.id and ai.is_deleted = 0
                 left join
             room_info ri
             on la.room_id = ri.id and ri.is_deleted = 0
                 left join
             payment_type pt
             on la.payment_type_id = pt.id and pt.is_deleted = 0
                 left join
             lease_term lt
             on la.lease_term_id = lt.id and lt.is_deleted = 0
        <where>
            la.is_deleted = 0
            <if test="queryVo.provinceId != null">
                and ai.province_id = #{queryVo.provinceId}
            </if>
            <if test="queryVo.cityId != null">
                and ai.city_id = #{queryVo.cityId}
            </if>
            <if test="queryVo.districtId != null">
                and ai.district_id = #{queryVo.districtId}
            </if>
            <if test="queryVo.apartmentId != null">
                and la.apartment_id = #{queryVo.apartmentId}
            </if>
            <if test="queryVo.roomNumber != null and queryVo.roomNumber != ''">
                and ri.room_number like concat('%',#{queryVo.roomNumber},'%')
            </if>
            <if test="queryVo.name != null and queryVo.name != ''">
                and la.name like concat('%',#{queryVo.name},'%')
            </if>
            <if test="queryVo.phone != null and queryVo.phone != ''">
                and la.phone like concat('%',#{queryVo.phone},'%')
            </if>
        </where>
    </select>
根据ID查询租约信息

Alt+Enter,实现方法

java 复制代码
public class LeaseAgreementServiceImpl extends ServiceImpl<LeaseAgreementMapper, LeaseAgreement>
        implements LeaseAgreementService {

    @Autowired
    private LeaseAgreementMapper leaseAgreementMapper;

    @Autowired
    private ApartmentInfoMapper apartmentInfoMapper;

    @Autowired
    private RoomInfoMapper roomInfoMapper;

    @Autowired
    private PaymentTypeMapper paymentTypeMapper;

    @Autowired
    private LeaseTermMapper leaseTermMapper;


    @Override
    public IPage<AgreementVo> pageAgreementByQuery(Page<AgreementVo> page, AgreementQueryVo queryVo) {
        return leaseAgreementMapper.pageAgreementByQuery(page, queryVo);
    }

    @Override
    public AgreementVo getAgreementById(Long id) {
        //1.查询租约信息
        LeaseAgreement leaseAgreement = leaseAgreementMapper.selectById(id);
        //2.查询公寓信息
        ApartmentInfo apartmentInfo = apartmentInfoMapper.selectById(leaseAgreement.getApartmentId());
        //3.查询房间信息
        RoomInfo roomInfo = roomInfoMapper.selectById(leaseAgreement.getRoomId());
        //4.查询支付方式
        PaymentType paymentType = paymentTypeMapper.selectById(leaseAgreement.getPaymentTypeId());
        //5.查询租期
        LeaseTerm leaseTerm = leaseTermMapper.selectById(leaseAgreement.getLeaseTermId());
        //6.组合
        AgreementVo agreementVo = new AgreementVo();
        BeanUtils.copyProperties(leaseAgreement, agreementVo);
        agreementVo.setApartmentInfo(apartmentInfo);
        agreementVo.setRoomInfo(roomInfo);
        agreementVo.setPaymentType(paymentType);
        agreementVo.setLeaseTerm(leaseTerm);
        return agreementVo;
    }
}
根据ID删除租约信息

成功

根据ID更新租约状态

成功

定时检查租约状态

4.用户管理

java 复制代码
@Autowired
private UserInfoService userInfoService;

根据条件分页查询用户列表

条件:手机号码、用户账号状态

java 复制代码
        LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.like(queryVo.getPhone()!= null,UserInfo::getPhone, queryVo.getPhone());
        queryWrapper.eq(queryVo.getStatus()!= null,UserInfo::getStatus, queryVo.getStatus());

分页查询

java 复制代码
   Page<UserInfo> page = new Page<>(current, size);
   ...
   Page<UserInfo> list = userInfoService.page(page, queryWrapper);
   return Result.ok(list);

测试

根据ID更新用户状态

java 复制代码
    @Operation(summary = "根据用户id更新账号状态")
    @PostMapping("updateStatusById")
    public Result updateStatusById(@RequestParam Long id, @RequestParam BaseStatus status) {
        LambdaUpdateWrapper<UserInfo> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(UserInfo::getId, id);
        updateWrapper.set(UserInfo::getStatus, status);
        userInfoService.update(updateWrapper);
        return Result.ok();
    }

5.系统管理

后台用户岗位管理

SystemPostController.java

java 复制代码
  @Autowired
private SystemPostService systemPostService;
分页查询岗位信息
java 复制代码
    @Operation(summary = "分页获取岗位信息")
    @GetMapping("page")
    private Result<IPage<SystemPost>> page(@RequestParam long current, @RequestParam long size) {
        IPage<SystemPost> page = new Page<>(current, size);
        IPage<SystemPost> list = systemPostService.page(page);
        return Result.ok(list);
    }

测试

保存或更新岗位信息
java 复制代码
    @Operation(summary = "保存或更新岗位信息")
    @PostMapping("saveOrUpdate")
    public Result saveOrUpdate(@RequestBody SystemPost systemPost) {
        systemPostService.saveOrUpdate(systemPost);
        return Result.ok();
    }
根据ID删除岗位信息
java 复制代码
    @DeleteMapping("deleteById")
    @Operation(summary = "根据id删除岗位")
    public Result removeById(@RequestParam Long id) {
        systemPostService.removeById(id);
        return Result.ok();
    }

测试

获取全部岗位列表
java 复制代码
    @Operation(summary = "获取全部岗位列表")
    @GetMapping("list")
    public Result<List<SystemPost>> list() {
        List<SystemPost> list = systemPostService.list();
        return Result.ok(list);
    }
根据ID获取岗位信息
java 复制代码
    @GetMapping("getById")
    @Operation(summary = "根据id获取岗位信息")
    public Result<SystemPost> getById(@RequestParam Long id) {
        SystemPost systemPost = systemPostService.getById(id);
        return Result.ok(systemPost);
    }
根据ID修改岗位状态
java 复制代码
    @Operation(summary = "根据岗位id修改状态")
    @PostMapping("updateStatusByPostId")
    public Result updateStatusByPostId(@RequestParam Long id, @RequestParam BaseStatus status) {
        LambdaUpdateWrapper<SystemPost> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(SystemPost::getId, id)
                .set(SystemPost::getStatus, status);
        return Result.ok();
    }

后台用户信息管理

java 复制代码
  @Autowired
    private SystemUserService systemUserService;
根据条件分页查询后台用户列表

用户和岗位表

java 复制代码
    @Operation(summary = "根据条件分页查询后台用户列表")
    @GetMapping("page")
    public Result<IPage<SystemUserItemVo>> page(@RequestParam long current, @RequestParam long size, SystemUserQueryVo queryVo) {
        IPage<SystemUser> page = new Page<>(current, size);
        IPage<SystemUserItemVo> list =  systemUserService.pageSystemUserByQuery(page, queryVo);
        return Result.ok(list);
    }

alt+enter、实现方法

SystemUserServiceImpl.java

java 复制代码
 @Autowired
    private SystemUserMapper systemUserMapper;

    @Override
    public IPage<SystemUserItemVo> pageSystemUserByQuery(IPage<SystemUser> page, SystemUserQueryVo queryVo) {
        return systemUserMapper.pageSystemUserByQuery(page, queryVo);
    }

alt+enter、Generate statement

java 复制代码
<select id="pageSystemUserByQuery"
            resultType="org.example.lease.web.admin.vo.system.user.SystemUserItemVo">
        select su.id,
               username,
               su.name,
               type,
               phone,
               avatar_url,
               additional_info,
               post_id,
               su.status,
               sp.name post_name
        from system_user su
                 left join system_post sp on su.post_id = sp.id and sp.is_deleted = 0
        <where>
            su.is_deleted = 0
            <if test="queryVo.name != null and queryVo.name != ''">
                and su.name like concat('%',#{queryVo.name},'%')
            </if>
            <if test="queryVo.phone !=null and queryVo.phone != ''">
                and su.phone like concat('%',#{queryVo.phone},'%')
            </if>
        </where>
    </select>

测试

根据ID查询后台用户信息
java 复制代码
    @Operation(summary = "根据ID查询后台用户信息")
    @GetMapping("getById")
    public Result<SystemUserItemVo> getById(@RequestParam Long id) {
        SystemUser list = systemUserService.getSystemUserById(id);
        return Result.ok();
    }

alt+enter、实现方法

SystemUserServiceImpl.java

java 复制代码
/**
     * 根据用户ID获取系统用户信息
     *
     * @param id 用户ID
     * @return SystemUserItemVo 包含用户信息的视图对象
     */
    @Override
    public SystemUserItemVo getSystemUserById(Long id) {
        // 通过ID查询系统用户信息
        SystemUser systemUser = systemUserMapper.selectById(id);
        // 根据用户中的职位ID查询职位信息
        SystemPost systemPost = systemPostMapper.selectById(systemUser.getPostId());

        // 创建用户信息视图对象
        SystemUserItemVo systemUserItemVo = new SystemUserItemVo();
        // 将职位信息复制到用户信息视图对象中
        BeanUtils.copyProperties(systemPost, systemUserItemVo);
        // 设置职位名称
        systemUserItemVo.setPostName(systemUserItemVo.getPostName());

        // 返回包含用户信息的视图对象
        return systemUserItemVo;
    }
保存或更新后台用户信息

密码通常会使用一些单向函数进行处理,常用于处理密码的单向函数(算法)有MD5、SHA-256等

Apache Commons是Apache软件基金会下的一个项目,其致力于提供可重用的开源软件,其中包含了很多易于使用的现成工具。

Apache Commons 提供了一个工具类DigestUtils

common模块的pom.xml中添加启动器

java 复制代码
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
</dependency>
java 复制代码
DigestUtils.md5Hex(systemUser.getPassword())
  • DigestUtils: Apache Commons Codec 提供的工具类,用于计算消息摘要(哈希值)
  • md5Hex(): 方法将输入字符串转换为32位的小写MD5哈希值
  • systemUser.getPassword(): 获取用户的密码
java 复制代码
systemUser.setPassword()// 设置加密后的密码

整体

java 复制代码
    @Operation(summary = "保存或更新后台用户信息")
    @PostMapping("saveOrUpdate")
    public Result saveOrUpdate(@RequestBody SystemUser systemUser) {
        if (systemUser.getPassword() != null) {
            systemUser.setPassword(DigestUtils.md5Hex(systemUser.getPassword()));
        }
        systemUserService.saveOrUpdate(systemUser);
        return Result.ok();
    }

Mybatis-Plus 更新策略

使用Mybatis-Plus提供的更新方法时,若实体中的字段为null,默认情况下,最终生成的update语句中,不会包含该字段。若想改变默认行为,可做以下配置:

全局配置

application.yml中配置如下参数

复制代码
mybatis-plus:
  global-config:
    db-config:
      update-strategy: <strategy>
  • ignore:忽略空值判断,不管字段是否为空,都会进行更新

  • not_null:进行非空判断,字段非空才会进行判断,默认值

  • not_empty:进行非空判断,并进行非空串("")判断,主要针对字符串类型

  • never:从不进行更新,不管该字段为何值,都不更新

  • strategy:可选值

局部配置

  • 在实体类中的具体字段通过@TableField注解进行配置,如下:

    复制代码
    @Schema(description = "密码")
    @TableField(value = "password", updateStrategy = FieldStrategy.NOT_EMPTY)
    private String password;

添加成功

判断后台用户名是否可用
java 复制代码
    @Operation(summary = "判断后台用户名是否可用")
    @GetMapping("isUserNameAvailable")
    public Result<Boolean> isUsernameExists(@RequestParam String username) {
        LambdaQueryWrapper<SystemUser> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SystemUser::getUsername, username);
        long count = systemUserService.count(queryWrapper);
        return Result.ok(count == 0);
    }
根据ID删除后台用户信息
java 复制代码
    @DeleteMapping("deleteById")
    @Operation(summary = "根据ID删除后台用户信息")
    public Result removeById(@RequestParam Long id) {
        systemUserService.removeById(id);
        return Result.ok();
    }
根据ID修改后台用户状态
java 复制代码
    @Operation(summary = "根据ID修改后台用户状态")
    @PostMapping("updateStatusByUserId")
    public Result updateStatusByUserId(@RequestParam Long id, @RequestParam BaseStatus status) {
        LambdaUpdateWrapper<SystemUser> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(SystemUser::getId, id).set(SystemUser::getStatus, status);
        systemUserService.update(updateWrapper);
        return Result.ok();
    }

6.登录管理

接口开发

LoginController.java

java 复制代码
    @Autowired
    private LoginService loginService;
获取图形验证码

验证码生成工具, 使用开源的验证码生成工具EasyCaptcha

common模块的pom.xml文件中增加如下启动器

复制代码
<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>

application.yml中增加如下配置

复制代码
spring:
  data:
    redis:
      host: <hostname>
      port: <port>
      database: 0

LoginController.java

java 复制代码
    @Operation(summary = "获取图形验证码")
    @GetMapping("login/captcha")
    public Result<CaptchaVo> getCaptcha() {
        CaptchaVo captcha = loginService.getCaptcha();
        return Result.ok(captcha);
    }

Alt+enter、实现方法

LoginServiceImpl.java

java 复制代码
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public CaptchaVo getCaptcha() {
        SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 4);

        String code = specCaptcha.text().toLowerCase();
        String key = "admin:login:" + UUID.randomUUID();
        String image = specCaptcha.toBase64();
        redisTemplate.opsForValue().set(key, code, 60, TimeUnit.SECONDS);

        return new CaptchaVo(image, key);
    }

为方便管理,可以将Reids相关的一些值定义为常量,例如key的前缀、TTL时长

common/src/main/java/org/example/lease/common/constant/RedisConstant.java

java 复制代码
public class RedisConstant {
    public static final String ADMIN_LOGIN_PREFIX = "admin:login:";
    public static final Integer ADMIN_LOGIN_CAPTCHA_TTL_SEC = 60;
    public static final String APP_LOGIN_PREFIX = "app:login:";
    public static final Integer APP_LOGIN_CODE_RESEND_TIME_SEC = 60;
    public static final Integer APP_LOGIN_CODE_TTL_SEC = 60 * 10;
    public static final String APP_ROOM_PREFIX = "app:room:";
}

LoginServiceImpl.java

java 复制代码
    @Override
    public CaptchaVo getCaptcha() {
        SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 4);

        String code = specCaptcha.text().toLowerCase();
        String key = RedisConstant.ADMIN_LOGIN_PREFIX + UUID.randomUUID();
        String image = specCaptcha.toBase64();
        redisTemplate.opsForValue().set(key, code, RedisConstant.ADMIN_LOGIN_CAPTCHA_TTL_SEC, TimeUnit.SECONDS);

        return new CaptchaVo(image, key);
    }
登录接口
  • 前端发送usernamepasswordcaptchaKeycaptchaCode请求登录。

LoginController.java

java 复制代码
    @Operation(summary = "登录")
    @PostMapping("login")
    public Result<String> login(@RequestBody LoginVo loginVo) {
        String token = loginService.login(loginVo);
        return Result.ok(token);
    }

alt+enter、实现方法

LoginServiceImpl.java

  • 判断captchaCode是否为空,若为空,则直接响应验证码为空;若不为空进行下一步判断。
java 复制代码
        //1.判断是否输入了验证码
        if (loginVo.getCaptchaCode() == null) {
            throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_NOT_FOUND);
        }
  • 根据captchaKey从Redis中查询之前保存的code,若查询出来的code为空,则直接响应验证码已过期;若不为空进行下一步判断。
java 复制代码
        //2.校验验证码
        String code = redisTemplate.opsForValue().get(loginVo.getCaptchaKey());

        if (code == null) {
            throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_EXPIRED);
        }
  • 比较captchaCodecode,若不相同,则直接响应验证码不正确;若相同则进行下一步判断。
java 复制代码
        if (!code.equals(loginVo.getCaptchaCode().toLowerCase())) {
            throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_ERROR);
        }
java 复制代码
    @Autowired
    private SystemUserMapper systemUserMapper;
  • 根据username查询数据库,若查询结果为空,则直接响应账号不存在;若不为空则进行下一步判断。
java 复制代码
        //3.校验用户是否存在
        LambdaQueryWrapper<SystemUser> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SystemUser::getUsername, loginVo.getUsername());
        SystemUser systemUser = systemUserMapper.selectOne(queryWrapper);
        if (systemUser == null) {
            throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_NOT_EXIST_ERROR);
        }
  • 查看用户状态,判断是否被禁用,若禁用,则直接响应账号被禁;若未被禁用,则进行下一步判断。
java 复制代码
        //4.校验用户是否被禁
        if (systemUser.getStatus() == BaseStatus.DISABLE) {
            throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_DISABLED_ERROR);
        }
  • 比对password和数据库中查询的密码,若不一致,则直接响应账号或密码错误,若一致则进行入最后一步。
java 复制代码
        //5.校验用户密码
        if (!systemUser.getPassword().equals(DigestUtils.md5Hex(loginVo.getPassword()))) {
            throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_ERROR);
        }
  • 创建JWT,并响应给浏览器。

引入Maven依赖

common模块的pom.xml文件中增加启动器

java 复制代码
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
</dependency>

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <scope>runtime</scope>
</dependency>

common/src/main/java/org/example/lease/common/utils/JwtUtil.java

java 复制代码
public static String creatToken(Long userId, String username) {
        SecretKey secretKey = Keys.hmacShaKeyFor("asdfghjkl".getBytes());
        String token = Jwts.builder()
                .setSubject("USER_INFO")
                .setExpiration(new Date(System.currentTimeMillis() + 3600000))
                .claim("userId", userId)
                .claim("username", username)
                .signWith(secretKey, SignatureAlgorithm.HS256)
                .compact();

        return token;

    }

测试

java 复制代码
    public static void main(String[] args) {
        System.out.println(creatToken(1l, "zhangsan"));
    }

JWT签名密钥的长度不符合安全要求

随机生成密码

LoginServiceImpl.java

java 复制代码
        return JwtUtil.creatToken(systemUser.getId(), systemUser.getUsername());

测试

原因

修改方法

alt+enter、Generate statement

java 复制代码
 select id,
               username,
               password,
               name,
               type,
               phone,
               avatar_url,
               additional_info,
               post_id,
               status
        from system_user
        where is_deleted = 0
          and username = #{username}

测试

编写HandlerInterceptor

我们需要为所有受保护的接口增加校验JWT合法性的逻辑。

JwtUtil.java

java 复制代码
public static void parseToken(String token) {
        try {
            JwtParser jwtParser = Jwts.parserBuilder().setSigningKey(secretKey).build();
            jwtParser.parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException e) {
            throw new LeaseException(ResultCodeEnum.TOKEN_EXPIRED);
        } catch (JwtException e) {
            throw new LeaseException(ResultCodeEnum.TOKEN_INVALID);
        }
    }

web-admin模块中创键

web/web-admin/src/main/java/org/example/lease/web/admin/custom/interceptor/AuthenticationInterceptor.java

java 复制代码
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("access-token");
        if (token == null) {
            throw new LeaseException(ResultCodeEnum.ADMIN_LOGIN_AUTH);
        }
        JwtUtil.parseToken(token);
        return true;
    }
}
java 复制代码
        if (token == null) {
            throw new LeaseException(ResultCodeEnum.ADMIN_LOGIN_AUTH);
        }

移到JwtUtil.java

Knife4j配置

在增加上述拦截器后,为方便继续调试其他接口,可以获取一个长期有效的Token,将其配置到Knife4j的全局参数中

java 复制代码
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this.authenticationInterceptor);
    }

测试

控制拦截器

WebMvcConfiguration.java

java 复制代码
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this.authenticationInterceptor).addPathPatterns("/admin/**");
    }

测试

控制拦截器

WebMvcConfiguration.java

java 复制代码
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(this.authenticationInterceptor).addPathPatterns("/admin/**").excludePathPatterns("/admin/login/**");
    }

测试

发送请求时会自动携带该header

生成测试用jwt

JwtUtil.java

java 复制代码
    public static void main(String[] args) {
        System.out.println(creatToken(2L, "user"));
    }
java 复制代码
 .setExpiration(new Date(System.currentTimeMillis() + 3600000*24*365L))

测试

需要每个分组设置全局参数

成功

获取登录用户个人信息

需要从Jwt中获取用户id

JwtUtil.java

LoginController.java

java 复制代码
    @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 = loginService.getLoginUserInfoById(userId);
        return Result.ok(userInfo);
    }

alt+enter、实现方法

java 复制代码
    @Override
    public SystemUserInfoVo getLoginUserInfoById(Long userId) {
        SystemUser systemUser = systemUserMapper.selectById(userId);
        SystemUserInfoVo systemUserInfoVo = new SystemUserInfoVo();
        systemUserInfoVo.setName(systemUser.getName());
        systemUserInfoVo.setAvatarUrl(systemUser.getAvatarUrl());
        return systemUserInfoVo;
    }

JWT会被重复解析两次,在拦截器将Token解析完毕后,将结果保存至ThreadLocal

common模块中创建common/src/main/java/org/example/lease/common/login/LoginUserHolder.java

java 复制代码
package org.example.lease.common.login;

public class LoginUserHolder {
    public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>();

    public static void setLoginUser(LoginUser loginUser) {
        threadLocal.set(loginUser);
    }

    public static LoginUser getLoginUser() {
        return threadLocal.get();
    }

    public static void clear() {
        threadLocal.remove();
    }
}

common/src/main/java/org/example/lease/common/login/LoginUser.java

java 复制代码
package org.example.lease.common.login;


import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class LoginUser {

    private Long userId;
    private String username;
}

修改AuthenticationInterceptor拦截器

java 复制代码
@Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("access-token");
        Claims clamis = JwtUtil.parseToken(token);
        Long userId = clamis.get("userId", Long.class);
        String username = clamis.get("username", String.class);
        LoginUserHolder.setLoginUser(new LoginUser(userId, username));
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        LoginUserHolder.clear();
    }

LoginController.java

java 复制代码
    @Operation(summary = "获取登陆用户个人信息")
    @GetMapping("info")
    public Result<SystemUserInfoVo> info() {
        Long userId = LoginUserHolder.getLoginUser().getUserId();
        SystemUserInfoVo userInfo = loginService.getLoginUserInfoById(userId);
        return Result.ok(userInfo);
    }
相关推荐
西岸行者7 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意7 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码7 天前
嵌入式学习路线
学习
毛小茛7 天前
计算机系统概论——校验码
学习
babe小鑫7 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms7 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下7 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。7 天前
2026.2.25监控学习
学习
im_AMBER7 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J7 天前
从“Hello World“ 开始 C++
c语言·c++·学习