2023-08-24 八股

八股理解+背诵:技术栈:Java后端开发

并发 (网络与线程)

Make a Connection: networking sockets and multithreading

创建线程的三种方式:

通过实现Runnable接口、实现Callable接口,以及继承Thread类的方式。

  1. 实现Runnable / Callable接口

    这种方式是实现了Java提供的Runnable接口或Callable接口来创建线程。这允许你在创建线程时保留类的继承关系,因为Java是单继承的。

    • Runnable接口:需要实现run()方法,线程的逻辑将在run()方法中定义。这种方式不能直接返回结果或抛出受检查的异常。
    • Callable接口:需要实现call()方法,类似于run()方法,但可以返回结果并抛出异常。

    对于Callable,你可以通过ExecutorServicesubmit()方法来提交任务,得到一个Future对象,通过这个对象可以等待任务完成并获取结果。

  2. 继承Thread类

    这种方式是通过继承Thread类来创建线程。在创建线程对象时,你需要创建一个新的类,继承Thread类,并重写run()方法,将线程的逻辑放在run()方法中。

    使用这种方式,你的线程类已经继承了Thread类,所以无法再继承其他类。你需要创建线程对象,然后调用start()方法来启动线程。

Thread thread = new MyThread();   //MyThread是继承自Thread的类
thread.start();

IO

什么是IO?

Java中的IO是指在应用程序中进行输入和输出操作的一种方式。在操作系统中,应用程序运行在用户空间,而进行系统级的输入输出操作需要访问内核空间的资源。为了进行IO操作,应用程序通过系统调用请求操作系统的帮助,间接访问内核空间。Java中的IO涵盖了文件IO(磁盘读写)和网络IO(网络请求和响应)。

从应用程序的视角来看,Java的IO操作实际上是发起系统调用请求操作系统的内核执行具体的IO操作。具体来说,Java的IO操作经历以下步骤:

  1. 应用程序发起IO调用请求,请求操作系统准备IO设备(例如文件或网络连接)的数据。
  2. 操作系统内核等待IO设备准备好数据。
  3. 一旦IO设备准备好数据,内核将数据从内核空间拷贝到应用程序的用户空间,这个过程涉及数据在内核空间和用户空间之间的复制。
  4. 应用程序可以在用户空间对这些数据进行处理。

综上所述,Java中的IO是通过系统调用来实现的,应用程序通过IO操作与操作系统内核交互,以完成文件读写和网络通信等功能。

异常

在Java中,异常可以被视为在程序执行过程中可能发生的意外或错误情况。所有的异常都是java.lang包中Throwable类的子类。Throwable类有两个主要的子类,分别是ExceptionError

  1. Exception: Exception是表示程序在运行过程中可能遇到的可处理异常的基类。Exception可以进一步分为两种类型:运行时异常(非受检查异常)和非运行时异常(受检查异常)。

    • 运行时异常: 也称为非受检查异常,是指在程序运行过程中可能会发生但编译器不会强制要求你处理的异常。例如,NullPointerExceptionArrayIndexOutOfBoundsException等。由于编译器不强制处理,所以你可以选择在代码中捕获和处理这些异常,但并不是必须的。

    • 非运行时异常: 也称为受检查异常,是指编译器会强制你处理的异常。例如,IOExceptionSQLException等。你必须在代码中使用try-catch块或者在方法签名中使用throws关键字来处理或传递这些异常。

  2. Error: Error是表示程序无法处理的严重问题或错误的基类。Error通常表示虚拟机或底层系统出现了问题,程序无法继续运行。这些错误是严重的,通常会导致应用程序被终止。例如,OutOfMemoryError(内存不足)、StackOverflowError(堆栈溢出)等。

综合而言,Exception表示程序可以处理的异常情况,而Error表示程序无法处理的严重问题。异常处理的目的是为了增加程序的稳定性和可靠性,使程序能够在出现问题时进行适当的处理,尽可能地继续执行下去。

牛客网项目

数据库设计

数据库表
community-init-sql

这个数据库表是在cmd中实现的:

source D:/work/community-init-sql/init_schema.sql;
show tables;
source D:/work/community-init-sql/init_data.sql;
show tables;
select * from user;
community数据库表

在IDEA中基于SpringBoot框架实现:

MyBatis入门

1. 在pom.xml文件中添加相应的数据库依赖: MySQL MyBatis

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.16</version>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.0.1</version>
</dependency>

2. 配置数据库连接:

# DataSourceProperties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/community?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
spring.datasource.username=root
spring.datasource.password=yourpassword
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.maximum-pool-size=15
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.idle-timeout=30000

# MybatisProperties
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.nowcoder.community.entity
mybatis.configuration.useGeneratedKeys=true
mybatis.configuration.mapUnderscoreToCamelCase=true

# logger 开发时调试使用 user-mapper.xml 没有代码提示
logging.level.com.nowcoder.community=debug

这段代码是 Spring Boot 中配置数据库连接的配置文件,通常存储在 application.propertiesapplication.yml 文件中。它设置了与数据库的连接信息,以及连接池的一些参数。这些配置的含义:

  • spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver:指定数据库驱动的类名,这里使用MySQL的驱动。
  • spring.datasource.url=jdbc:mysql://localhost:3306/community?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong:指定数据库的URL,其中包括数据库的地址、端口、数据库名等,以及一些连接选项(字符编码、不使用SSL、服务器时区)。
  • spring.datasource.username=root:指定连接数据库所需的用户名。
  • spring.datasource.password=yourpassword:指定连接数据库所需的密码。
  • spring.datasource.type=com.zaxxer.hikari.HikariDataSource:指定使用的连接池类型,这里使用了 HikariCP 连接池。(访问数据库是需要使用连接池的)
  • spring.datasource.hikari.maximum-pool-size=15:设置连接池的最大连接数。
  • spring.datasource.hikari.minimum-idle=5:设置连接池的最小空闲连接数。
  • spring.datasource.hikari.idle-timeout=30000:设置连接池中连接的最大空闲时间,超过该时间的空闲连接将被回收。
  • mybatis.mapper-locations=classpath:mapper/*.xml:这个属性指定了MyBatis的XML映射文件的位置。通常,这些XML文件包含了SQL映射语句,定义了数据库操作。这里的配置表示映射文件位于 "classpath:mapper" 目录下,且文件名以 ".xml" 结尾。(在Resources文件夹下创建mapper包)
  • mybatis.type-aliases-package=com.nowcoder.community.entity:这个属性用于指定包路径,MyBatis将在这个包路径下查找Java类,用于设置别名。这些别名可以用于映射结果的实体类,使得SQL查询结果可以自动映射到指定的Java对象。
  • mybatis.configuration.useGeneratedKeys=true:这个属性设置为 "true",表示在插入数据时,MyBatis会自动获取数据库生成的主键值(1,2,3,4),并将其设置到Java对象的主键字段中。
  • mybatis.configuration.mapUnderscoreToCamelCase=true:这个属性设置为 "true",表示在数据库列名中的下划线命名法(如 user_name)和Java对象属性的驼峰命名法(如 userName)之间进行自动映射。

3. 创建实体类: User.java

package com.nowcoder.community.entity;

import java.util.Date;

public class User {

    private int id;
    private String username;
    private String password;
    private String salt;
    private String email;
    private int type;
    private int status;
    private String activationCode;
    private String headerUrl;
    private Date createTime;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getActivationCode() {
        return activationCode;
    }

    public void setActivationCode(String activationCode) {
        this.activationCode = activationCode;
    }

    public String getHeaderUrl() {
        return headerUrl;
    }

    public void setHeaderUrl(String headerUrl) {
        this.headerUrl = headerUrl;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", salt='" + salt + '\'' +
                ", email='" + email + '\'' +
                ", type=" + type +
                ", status=" + status +
                ", activationCode='" + activationCode + '\'' +
                ", headerUrl='" + headerUrl + '\'' +
                ", createTime=" + createTime +
                '}';
    }

}

4. 创建持久化操作(Mapper):
持久化操作是指将应用程序的数据存储到持久存储介质(如数据库)中,以便数据在应用程序的不同运行周期中得以保留。在这里,这些接口方法定义了数据库的CRUD操作,包括插入、查询和更新等UserMapper.java

package com.nowcoder.community.dao;

import com.nowcoder.community.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {

    User selectById(int id);

    User selectByName(String username);

    User selectByEmail(String email);

    int insertUser(User user);

    int updateStatus(int id, int status);

    int updateHeader(int id, String headerUrl);

    int updatePassword(int id, String password);

}

@Mapper 注解表示这个接口是一个MyBatis的Mapper接口,MyBatis会自动扫描并为其生成实现。这样,您可以通过调用这些方法来进行与用户相关的数据库操作,而不需要手动编写SQL语句。

5. 创建MyBatis Mapper配置文件,实现用户表的增删改查操作。user-mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nowcoder.community.dao.UserMapper">

    <sql id="insertFields">
        username, password, salt, email, type, status, activation_code, header_url, create_time
    </sql>

    <sql id="selectFields">
        id, username, password, salt, email, type, status, activation_code, header_url, create_time
    </sql>

    <select id="selectById" resultType="User">
        select <include refid="selectFields"></include>
        from user
        where id = #{id}
    </select>

    <select id="selectByName" resultType="User">
        select <include refid="selectFields"></include>
        from user
        where username = #{username}
    </select>

    <select id="selectByEmail" resultType="User">
        select <include refid="selectFields"></include>
        from user
        where email = #{email}
    </select>

    <insert id="insertUser" parameterType="User" keyProperty="id">
        insert into user (<include refid="insertFields"></include>)
        values(#{username}, #{password}, #{salt}, #{email}, #{type}, #{status}, #{activationCode}, #{headerUrl}, #{createTime})
    </insert>

    <update id="updateStatus">
        update user set status = #{status} where id = #{id}
    </update>

    <update id="updateHeader">
        update user set header_url = #{headerUrl} where id = #{id}
    </update>

    <update id="updatePassword">
        update user set password = #{password} where id = #{id}
    </update>

</mapper>

6. 编写测试类(先测试看看)

package com.nowcoder.community;

import com.nowcoder.community.dao.DiscussPostMapper;
import com.nowcoder.community.dao.LoginTicketMapper;
import com.nowcoder.community.dao.MessageMapper;
import com.nowcoder.community.dao.UserMapper;
import com.nowcoder.community.entity.DiscussPost;
import com.nowcoder.community.entity.LoginTicket;
import com.nowcoder.community.entity.Message;
import com.nowcoder.community.entity.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Date;
import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class MapperTests {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private DiscussPostMapper discussPostMapper;

    @Autowired
    private LoginTicketMapper loginTicketMapper;

    @Autowired
    private MessageMapper messageMapper;

    @Test
    public void testSelectUser() {
        User user = userMapper.selectById(101);
        System.out.println(user);

        user = userMapper.selectByName("liubei");
        System.out.println(user);

        user = userMapper.selectByEmail("nowcoder101@sina.com");
        System.out.println(user);
    }

    @Test
    public void testInsertUser() {
        User user = new User();
        user.setUsername("test");
        user.setPassword("123456");
        user.setSalt("abc");
        user.setEmail("test@qq.com");
        user.setHeaderUrl("http://www.nowcoder.com/101.png");
        user.setCreateTime(new Date());

        int rows = userMapper.insertUser(user);
        System.out.println(rows);
        System.out.println(user.getId());
    }

    @Test
    public void updateUser() {
        int rows = userMapper.updateStatus(150, 1);
        System.out.println(rows);

        rows = userMapper.updateHeader(150, "http://www.nowcoder.com/102.png");
        System.out.println(rows);

        rows = userMapper.updatePassword(150, "hello");
        System.out.println(rows);
    }

    @Test
    public void testSelectPosts() {
        List<DiscussPost> list = discussPostMapper.selectDiscussPosts(149, 0, 10, 0);
        for (DiscussPost post : list) {
            System.out.println(post);
        }

        int rows = discussPostMapper.selectDiscussPostRows(149);
        System.out.println(rows);
    }

    @Test
    public void testInsertLoginTicket() {
        LoginTicket loginTicket = new LoginTicket();
        loginTicket.setUserId(101);
        loginTicket.setTicket("abc");
        loginTicket.setStatus(0);
        loginTicket.setExpired(new Date(System.currentTimeMillis() + 1000 * 60 * 10));

        loginTicketMapper.insertLoginTicket(loginTicket);
    }

    @Test
    public void testSelectLoginTicket() {
        LoginTicket loginTicket = loginTicketMapper.selectByTicket("abc");
        System.out.println(loginTicket);

        loginTicketMapper.updateStatus("abc", 1);
        loginTicket = loginTicketMapper.selectByTicket("abc");
        System.out.println(loginTicket);
    }

    @Test
    public void testSelectLetters() {
        List<Message> list = messageMapper.selectConversations(111, 0, 20);
        for (Message message : list) {
            System.out.println(message);
        }

        int count = messageMapper.selectConversationCount(111);
        System.out.println(count);

        list = messageMapper.selectLetters("111_112", 0, 10);
        for (Message message : list) {
            System.out.println(message);
        }

        count = messageMapper.selectLetterCount("111_112");
        System.out.println(count);

        count = messageMapper.selectLetterUnreadCount(131, "111_131");
        System.out.println(count);

    }

}

7. 创建控制器(Controller):
创建用于处理HTTP请求的控制器类,使用 @RestController (前后端分离,适用于构建RESTful API)或 @Controller 注解,根据业务需求编写请求处理方法。UserController

package com.nowcoder.community.controller;

import com.nowcoder.community.annotation.LoginRequired;
import com.nowcoder.community.entity.User;
import com.nowcoder.community.service.FollowService;
import com.nowcoder.community.service.LikeService;
import com.nowcoder.community.service.UserService;
import com.nowcoder.community.util.CommunityConstant;
import com.nowcoder.community.util.CommunityUtil;
import com.nowcoder.community.util.HostHolder;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

@Controller
@RequestMapping("/user")
public class UserController implements CommunityConstant {

    private static final Logger logger = LoggerFactory.getLogger(UserController.class);

    @Value("${community.path.upload}")
    private String uploadPath;

    @Value("${community.path.domain}")
    private String domain;

    @Value("${server.servlet.context-path}")
    private String contextPath;

    @Autowired
    private UserService userService;

    @Autowired
    private HostHolder hostHolder;

    @Autowired
    private LikeService likeService;

    @Autowired
    private FollowService followService;

    @LoginRequired
    @RequestMapping(path = "/setting", method = RequestMethod.GET)
    public String getSettingPage() {
        return "/site/setting";
    }

    @LoginRequired
    @RequestMapping(path = "/upload", method = RequestMethod.POST)
    public String uploadHeader(MultipartFile headerImage, Model model) {
        if (headerImage == null) {
            model.addAttribute("error", "您还没有选择图片!");
            return "/site/setting";
        }

        String fileName = headerImage.getOriginalFilename();
        String suffix = fileName.substring(fileName.lastIndexOf("."));
        if (StringUtils.isBlank(suffix)) {
            model.addAttribute("error", "文件的格式不正确!");
            return "/site/setting";
        }

        // 生成随机文件名
        fileName = CommunityUtil.generateUUID() + suffix;
        // 确定文件存放的路径
        File dest = new File(uploadPath + "/" + fileName);
        try {
            // 存储文件
            headerImage.transferTo(dest);
        } catch (IOException e) {
            logger.error("上传文件失败: " + e.getMessage());
            throw new RuntimeException("上传文件失败,服务器发生异常!", e);
        }

        // 更新当前用户的头像的路径(web访问路径)
        // http://localhost:8080/community/user/header/xxx.png
        User user = hostHolder.getUser();
        String headerUrl = domain + contextPath + "/user/header/" + fileName;
        userService.updateHeader(user.getId(), headerUrl);

        return "redirect:/index";
    }

    @RequestMapping(path = "/header/{fileName}", method = RequestMethod.GET)
    public void getHeader(@PathVariable("fileName") String fileName, HttpServletResponse response) {
        // 服务器存放路径
        fileName = uploadPath + "/" + fileName;
        // 文件后缀
        String suffix = fileName.substring(fileName.lastIndexOf("."));
        // 响应图片
        response.setContentType("image/" + suffix);
        try (
                FileInputStream fis = new FileInputStream(fileName);
                OutputStream os = response.getOutputStream();
        ) {
            byte[] buffer = new byte[1024];
            int b = 0;
            while ((b = fis.read(buffer)) != -1) {
                os.write(buffer, 0, b);
            }
        } catch (IOException e) {
            logger.error("读取头像失败: " + e.getMessage());
        }
    }

    // 个人主页
    @RequestMapping(path = "/profile/{userId}", method = RequestMethod.GET)
    public String getProfilePage(@PathVariable("userId") int userId, Model model) {
        User user = userService.findUserById(userId);
        if (user == null) {
            throw new RuntimeException("该用户不存在!");
        }

        // 用户
        model.addAttribute("user", user);
        // 点赞数量
        int likeCount = likeService.findUserLikeCount(userId);
        model.addAttribute("likeCount", likeCount);

        // 关注数量
        long followeeCount = followService.findFolloweeCount(userId, ENTITY_TYPE_USER);
        model.addAttribute("followeeCount", followeeCount);
        // 粉丝数量
        long followerCount = followService.findFollowerCount(ENTITY_TYPE_USER, userId);
        model.addAttribute("followerCount", followerCount);
        // 是否已关注
        boolean hasFollowed = false;
        if (hostHolder.getUser() != null) {
            hasFollowed = followService.hasFollowed(hostHolder.getUser().getId(), ENTITY_TYPE_USER, userId);
        }
        model.addAttribute("hasFollowed", hasFollowed);

        return "/site/profile";
    }

}

8.创建服务(Service):
创建用于业务逻辑处理的服务类,使用 @Service 注解,将业务逻辑从控制器中抽离出来,保持代码的组织结构清晰。

package com.nowcoder.community.service;

import com.nowcoder.community.dao.UserMapper;
import com.nowcoder.community.entity.LoginTicket;
import com.nowcoder.community.entity.User;
import com.nowcoder.community.util.CommunityConstant;
import com.nowcoder.community.util.CommunityUtil;
import com.nowcoder.community.util.MailClient;
import com.nowcoder.community.util.RedisKeyUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class UserService implements CommunityConstant {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private MailClient mailClient;

    @Autowired
    private TemplateEngine templateEngine;

    @Value("${community.path.domain}")
    private String domain;

    @Value("${server.servlet.context-path}")
    private String contextPath;

//    @Autowired
//    private LoginTicketMapper loginTicketMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    public User findUserById(int id) {
//        return userMapper.selectById(id);
        User user = getCache(id);
        if (user == null) {
            user = initCache(id);
        }
        return user;
    }

    public Map<String, Object> register(User user) {
        Map<String, Object> map = new HashMap<>();

        // 空值处理
        if (user == null) {
            throw new IllegalArgumentException("参数不能为空!");
        }
        if (StringUtils.isBlank(user.getUsername())) {
            map.put("usernameMsg", "账号不能为空!");
            return map;
        }
        if (StringUtils.isBlank(user.getPassword())) {
            map.put("passwordMsg", "密码不能为空!");
            return map;
        }
        if (StringUtils.isBlank(user.getEmail())) {
            map.put("emailMsg", "邮箱不能为空!");
            return map;
        }

        // 验证账号
        User u = userMapper.selectByName(user.getUsername());
        if (u != null) {
            map.put("usernameMsg", "该账号已存在!");
            return map;
        }

        // 验证邮箱
        u = userMapper.selectByEmail(user.getEmail());
        if (u != null) {
            map.put("emailMsg", "该邮箱已被注册!");
            return map;
        }

        // 注册用户
        user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
        user.setPassword(CommunityUtil.md5(user.getPassword() + user.getSalt()));
        user.setType(0);
        user.setStatus(0);
        user.setActivationCode(CommunityUtil.generateUUID());
        user.setHeaderUrl(String.format("https://images.nowcoder.com/head/%dt.png", new Random().nextInt(1000)));
        user.setCreateTime(new Date());
        userMapper.insertUser(user);

        // 激活邮件
        Context context = new Context();
        context.setVariable("email", user.getEmail());
        // http://localhost:8080/community/activation/101/code
        String url = domain + contextPath + "/activation/" + user.getId() + "/" + user.getActivationCode();
        context.setVariable("url", url);
        String content = templateEngine.process("/mail/activation", context);
        mailClient.sendMail(user.getEmail(), "激活账号", content);

        return map;
    }

    public int activation(int userId, String code) {
        User user = userMapper.selectById(userId);
        if (user.getStatus() == 1) {
            return ACTIVATION_REPEAT;
        } else if (user.getActivationCode().equals(code)) {
            userMapper.updateStatus(userId, 1);
            clearCache(userId);
            return ACTIVATION_SUCCESS;
        } else {
            return ACTIVATION_FAILURE;
        }
    }

    public Map<String, Object> login(String username, String password, long expiredSeconds) {
        Map<String, Object> map = new HashMap<>();

        // 空值处理
        if (StringUtils.isBlank(username)) {
            map.put("usernameMsg", "账号不能为空!");
            return map;
        }
        if (StringUtils.isBlank(password)) {
            map.put("passwordMsg", "密码不能为空!");
            return map;
        }

        // 验证账号
        User user = userMapper.selectByName(username);
        if (user == null) {
            map.put("usernameMsg", "该账号不存在!");
            return map;
        }

        // 验证状态
        if (user.getStatus() == 0) {
            map.put("usernameMsg", "该账号未激活!");
            return map;
        }

        // 验证密码
        password = CommunityUtil.md5(password + user.getSalt());
        if (!user.getPassword().equals(password)) {
            map.put("passwordMsg", "密码不正确!");
            return map;
        }

        // 生成登录凭证
        LoginTicket loginTicket = new LoginTicket();
        loginTicket.setUserId(user.getId());
        loginTicket.setTicket(CommunityUtil.generateUUID());
        loginTicket.setStatus(0);
        loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));
//        loginTicketMapper.insertLoginTicket(loginTicket);

        String redisKey = RedisKeyUtil.getTicketKey(loginTicket.getTicket());
        redisTemplate.opsForValue().set(redisKey, loginTicket);

        map.put("ticket", loginTicket.getTicket());
        return map;
    }

    public void logout(String ticket) {
//        loginTicketMapper.updateStatus(ticket, 1);
        String redisKey = RedisKeyUtil.getTicketKey(ticket);
        LoginTicket loginTicket = (LoginTicket) redisTemplate.opsForValue().get(redisKey);
        loginTicket.setStatus(1);
        redisTemplate.opsForValue().set(redisKey, loginTicket);
    }

    public LoginTicket findLoginTicket(String ticket) {
//        return loginTicketMapper.selectByTicket(ticket);
        String redisKey = RedisKeyUtil.getTicketKey(ticket);
        return (LoginTicket) redisTemplate.opsForValue().get(redisKey);
    }

    public int updateHeader(int userId, String headerUrl) {
//        return userMapper.updateHeader(userId, headerUrl);
        int rows = userMapper.updateHeader(userId, headerUrl);
        clearCache(userId);
        return rows;
    }

    public User findUserByName(String username) {
        return userMapper.selectByName(username);
    }

    // 1.优先从缓存中取值
    private User getCache(int userId) {
        String redisKey = RedisKeyUtil.getUserKey(userId);
        return (User) redisTemplate.opsForValue().get(redisKey);
    }

    // 2.取不到时初始化缓存数据
    private User initCache(int userId) {
        User user = userMapper.selectById(userId);
        String redisKey = RedisKeyUtil.getUserKey(userId);
        redisTemplate.opsForValue().set(redisKey, user, 3600, TimeUnit.SECONDS);
        return user;
    }

    // 3.数据变更时清除缓存数据
    private void clearCache(int userId) {
        String redisKey = RedisKeyUtil.getUserKey(userId);
        redisTemplate.delete(redisKey);
    }

    public Collection<? extends GrantedAuthority> getAuthorities(int userId) {
        User user = this.findUserById(userId);

        List<GrantedAuthority> list = new ArrayList<>();
        list.add(new GrantedAuthority() {

            @Override
            public String getAuthority() {
                switch (user.getType()) {
                    case 1:
                        return AUTHORITY_ADMIN;
                    case 2:
                        return AUTHORITY_MODERATOR;
                    default:
                        return AUTHORITY_USER;
                }
            }
        });
        return list;
    }

}

以上示例展示了如何在Spring Boot中配置数据库连接、创建实体类、持久化操作、控制器和服务。

Spring MVC三层架构 - 表现层、业务层、数据访问层是什么?

Spring MVC三层架构是一种用于构建Web应用程序的软件设计模式,它将应用程序划分为不同的层次,以实现模块化、可维护和可扩展的架构。这三个层次分别是:


spring-web-mvc-model-view-controller-introduction-and-features
  1. 表现层(Presentation Layer):【外观和装修】

    • 表现层是与用户界面相关的部分,负责处理用户的请求和展示数据给用户。它包括Web页面、视图模板、用户界面控制等内容。
    • 在Spring MVC中,表现层主要由控制器(Controller)负责。控制器接收用户请求,协调其他层的工作,并将处理结果传递给视图模板,最终呈现给用户。
  2. 业务层(Business Layer):【内部工作】

    • 业务层是应用程序的核心,包含了业务逻辑和处理数据的方法。它处理业务规则、流程和算法等,以及与数据无关的应用逻辑。
    • 在Spring MVC中,业务层通常由服务(Service)组件实现。服务组件将业务逻辑封装在方法中,可以被控制器调用,同时还可以被其他服务调用,实现代码的重用和解耦。
  3. 数据访问层(Data Access Layer):【储存和获取】

    • 数据访问层负责与数据库或其他数据存储进行交互,执行数据的读取、写入、更新和删除等操作。
    • 在Spring MVC中,数据访问层通常由持久层(Persistence Layer)来实现,使用持久化框架如Hibernate、JPA或MyBatis来简化数据库操作。

这种三层架构有助于分离关注点,使不同的功能模块相互独立。控制器(表现层)决定用户界面是什么样,服务(业务层)处理核心业务逻辑,持久层(数据访问层)处理与数据库的交互。这样的分层设计使得应用程序更加模块化、可维护和可扩展,同时也有助于团队协作和代码重用。


牛客 课程首页 高薪求职项目课
Spring Web MVC (Model View Controller) Introduction and Features

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容