仿牛客网项目---用户注册登录功能的实现

从今天开始我们来写一个新项目,这个项目是一个完整的校园论坛的项目。主要功能模块:用户登录注册,帖子发布和热帖排行,点赞关注,发送私信,消息通知,社区搜索等。这篇文章我们先试着写一下用户的登录注册功能。

我们做web项目,一般web项目是主要解决浏览器和服务器之间交互的问题。而浏览器和服务器是由一次一次的请求交互的。因此,任何功能都可拆解成若干次请求,其实只要掌握好每次请求的执行过程,按照步骤开发每一次请求,基本上项目就可以逐步完善起来。

一次请求的执行过程:

其实最好是可以把功能做拆解,第一步先实现什么效果,第二步再完善这个效果。因为你不可能一下子就一步到位写出完整的代码的,是吧?

所以这就是我的思路,我是想着一个功能一个功能的搞定。这篇文章来搞定用户登录注册。

用户登录注册功能,说实话,这个功能是真的比较复杂的,所以我们必须把这个功能拆解成若干部分,使得每部分都简单点,易于开发。对于web项目来说,每个功能都可以按照请求去拆解,因为每个功能都是由若干个浏览器和服务器的请求交互所构成的。比如注册功能,怎么访问和使用呢?比如你首页点击“注册”这个链接,就能打开注册的页面,那这个很显然就是访问服务器,就有一次请求,只不过这次请求就是打开页面而已,比较简单。第二个请求就是填写数据(填账号密码邮箱这些),然后点击“注册”按钮,那么这个数据就会提交给服务器,服务器就把这些数据存起来。之后,服务器就会给你一个邮件,邮件里面是链接,你点击那个链接就可以激活账号了,事实上,你点链接也是访问服务器,进行激活的服务。我们在写代码时还是按照DAO->Service->Controller来开发,但是有些功能可能没有DAO层,有些功能可能没有Service层,这都很正常。比如第一次请求:访问注册页面。你就访问个注册页面而已,哪有什么业务,什么访问数据库这些。

OK!接下来就开始写了!

用户注册登录模块大致结构

entity文件夹:User.java LoginTicket.java

DAO层:UserMapper   LoginMapper

Service层:UserService

Controller层:LoginController

entity文件夹---User.java

下面是entity文件夹下一个名为User.java的实体类文件。该文件定义了一个名为User的实体类,用于表示用户的信息。

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; //用户头像的URL地址
    private Date createTime;  //用户创建的时间戳,表示用户的注册时间或创建时间

   // 省略了getter和setter方法
   
    @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 +
                '}';
    }

}

这个实体类主要用于封装用户相关的信息,并在系统中进行传递和处理。它可以作为数据库表的映射实体类,也可以用于数据传输和业务逻辑操作。

entity文件夹---LoginTicket.java

下面是entity文件夹下一个名为LoginTicket.java的实体类文件。该文件定义了一个名为LoginTicket的实体类,用于表示登录凭证的信息。

对于用户登录凭证,我之前一直搞不懂,所以就查百度,感觉有一个例子很好的解释了什么是吧用户登录凭证,我分享给大家:

假设有一个在线购物网站,用户在该网站上注册账号并进行购物。当用户想要登录到他们的账号时,他们需要提供用户名和密码。在成功验证用户名和密码后,网站会颁发给用户一个登录凭证。

在这个例子中,登录凭证可以是一个唯一的令牌或会话ID。这个凭证会与用户的账号进行关联,并在用户的浏览器中存储为一个Cookie或其他形式的标识。

用户在登录凭证的帮助下,在一段时间内可以访问网站的受限资源,如个人购物车、订单历史记录等。每当用户访问需要身份验证的页面时,他们的凭证将被检查以验证其身份和权限。如果凭证有效且与用户账号匹配,用户将被授权访问相应的资源。

在此过程中,登录凭证充当了用户的身份标识,用于验证用户的身份并授权其访问网站的特定功能和信息。凭证的生成和验证是网站实现用户认证和会话管理的重要部分。

需要注意的是,每次用户登录时,会生成一个新的登录凭证,并在一段时间后过期。这样可以提高系统的安全性,并确保用户的身份唯一性。

package com.nowcoder.community.entity;

import java.util.Date;

public class LoginTicket {

    private int id; //登录凭证的唯一标识符
    private int userId;  //与登录凭证相关联的用户的ID
    private String ticket;  //登录凭证的字符串值
    private int status;   //登录凭证的状态,表示凭证的有效性或失效状态
    private Date expired;  //登录凭证的过期时间,表示凭证的有效期限

    // 省略了getter和setter方法
    
    @Override
    public String toString() {
        return "LoginTicket{" +
                "id=" + id +
                ", userId=" + userId +
                ", ticket='" + ticket + '\'' +
                ", status=" + status +
                ", expired=" + expired +
                '}';
    }
}

这些属性太晦涩了,我举个例子帮助大家理解。

  • id:假设用户A在登录后生成了一个登录凭证,系统为该凭证分配了一个唯一的ID,比如1。
  • userId:用户A的ID是123,这个属性将与用户A的ID关联起来,表示登录凭证与用户A的关联。
  • ticket:系统生成了一个字符串作为登录凭证,比如"abc123",这个字符串将在用户登录时颁发给用户A。
  • status:登录凭证的状态属性,用于标识凭证的有效性。比如,0表示凭证失效,1表示凭证有效。
  • expired:登录凭证的过期时间,比如2024-03-01 00:00:00,表示凭证在这个时间之后将失效。

DAO层---UserMapper

下面的代码是一个名为UserMapper的数据访问对象(DAO)接口,用于与用户数据进行交互。该接口使用MyBatis注解进行映射,用于定义与用户数据相关的数据库操作方法。


@Mapper
public interface UserMapper {

    User selectById(int id);
    //根据用户ID查询用户信息,并返回一个User对象

    User selectByName(String username);
    //根据用户名查询用户信息,并返回一个User对象

    User selectByEmail(String email);
    //根据电子邮件地址查询用户信息,并返回一个User对象

    int insertUser(User user);
    //插入一个新用户的信息,并返回插入的行数

    int updateStatus(int id, int status);
    //更新用户的状态信息(例如激活状态),并返回更新的行数

    int updateHeader(int id, String headerUrl);
    //更新用户的头像URL信息,并返回更新的行数

    int updatePassword(int id, String password);
    //更新用户的密码信息,并返回更新的行数

}

"返回更新的行数"是指在执行数据库更新操作后,影响到的数据库行数。例如,如果执行更新操作后返回的值为1,则表示成功更新了一行数据。

DAO层---LoginTicketMapper

下面的代码是一个名为LoginTicketMapper的数据访问对象(DAO)接口,用于与登录凭证数据进行交互。该接口使用了MyBatis的注解来定义与数据库的交互操作。

@Mapper
@Deprecated
public interface LoginTicketMapper {

    @Insert({
            "insert into login_ticket(user_id,ticket,status,expired) ",
            "values(#{userId},#{ticket},#{status},#{expired})"
    })
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insertLoginTicket(LoginTicket loginTicket);
//向数据库中插入一条登录凭证的信息,并返回插入的行数。使用了@Insert注解定义了插入操作的SQL语句
   

    @Select({
            "select id,user_id,ticket,status,expired ",
            "from login_ticket where ticket=#{ticket}"
    })
    LoginTicket selectByTicket(String ticket);
//根据登录凭证的字符串ticket从数据库中查询对应的登录凭证信息,并返回一个LoginTicket对象。使用了@Select注解定义了查询操作的SQL语句。

    @Update({
            "<script>",
            "update login_ticket set status=#{status} where ticket=#{ticket} ",
            "<if test=\"ticket!=null\"> ",
            "and 1=1 ",
            "</if>",
            "</script>"
    })
    int updateStatus(String ticket, int status);
//根据登录凭证的字符串ticket更新对应登录凭证的状态信息,并返回更新的行数。使用了@Update注解定义了更新操作的SQL语句。

}

Service层---UserService

下面的代码名叫UserService,其中包含了用户注册、登录、激活、注销等功能的实现。


@Service
public class UserService implements CommunityConstant {

     //省略了一些导入包和导入依赖项的代码,直接写方法


    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("http://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;
    }

}
  1. 实现了findUserById方法,根据用户ID查找用户信息。在这个方法中,首先尝试从缓存中获取用户信息,如果缓存中没有,则从数据库中查询,并将结果存入缓存中。
  2. 实现了register方法,用于用户注册。在这个方法中,首先进行参数的空值处理和账号、邮箱的验证。然后设置用户的一些属性,如盐值、密码加密、激活码等,并将用户信息插入数据库中。最后发送激活邮件给用户。
  3. 实现了activation方法,用于用户账号激活。根据传入的用户ID和激活码,验证用户的激活状态和激活码是否匹配,如果匹配则更新用户状态为已激活,并清除缓存中的用户信息。
  4. 实现了login方法,用户用户登录。在这个方法中,首先进行参数的空值处理和账号、密码的验证。然后验证账号的状态和密码的正确性。最后生成登录凭证(LoginTicket),将凭证信息存入Redis中,并返回给客户端。
  5. 实现了logout方法,用户用户注销。根据传入的登录凭证,将凭证的状态更新为已注销,并在Redis中更新相应的信息。
  6. 实现了findLoginTicket方法,根据登录凭证查找登录凭证的信息。
  7. 实现了updateHeader方法,用于更新用户的头像信息。在更新用户头像后,清除缓存中的用户信息。
  8. 实现了findUserByName方法,根据用户名查找用户信息。
  9. 实现了getCache方法,用于从缓存中获取用户信息。
  10. 实现了initCache方法,用于初始化缓存中的用户信息。
  11. 实现了clearCache方法,用于清除缓存中的用户信息。
  12. 实现了getAuthorities方法,用于获取用户的权限信息这就是展示了一个用户登录注册功能中,用户服务类的实现,涵盖了用户注册、登录、激活、注销等功能,并使用了缓存和Redis存储登录凭证的信息。

Controller层---LoginController

@Controller
public class LoginController implements CommunityConstant {
      
    //省略了一些配置依赖关系的的代码
 
    @RequestMapping(path = "/register", method = RequestMethod.GET)
    public String getRegisterPage() {
        return "/site/register";
    }
 
    @RequestMapping(path = "/login", method = RequestMethod.GET)
    public String getLoginPage() {
        return "/site/login";
    }
 
    @RequestMapping(path = "/register", method = RequestMethod.POST)
    public String register(Model model, User user) {
        Map<String, Object> map = userService.register(user);
        if (map == null || map.isEmpty()) {
            model.addAttribute("msg", "注册成功,我们已经向您的邮箱发送了一封激活邮件,请尽快激活!");
            model.addAttribute("target", "/index");
            return "/site/operate-result";
        } else {
            model.addAttribute("usernameMsg", map.get("usernameMsg"));
            model.addAttribute("passwordMsg", map.get("passwordMsg"));
            model.addAttribute("emailMsg", map.get("emailMsg"));
            return "/site/register";
        }
    }
 
    // http://localhost:8080/community/activation/101/code
    @RequestMapping(path = "/activation/{userId}/{code}", method = RequestMethod.GET)
    public String activation(Model model, @PathVariable("userId") int userId, @PathVariable("code") String code) {
        int result = userService.activation(userId, code);
        if (result == ACTIVATION_SUCCESS) {
            model.addAttribute("msg", "激活成功,您的账号已经可以正常使用了!");
            model.addAttribute("target", "/login");
        } else if (result == ACTIVATION_REPEAT) {
            model.addAttribute("msg", "无效操作,该账号已经激活过了!");
            model.addAttribute("target", "/index");
        } else {
            model.addAttribute("msg", "激活失败,您提供的激活码不正确!");
            model.addAttribute("target", "/index");
        }
        return "/site/operate-result";
    }
 
    @RequestMapping(path = "/kaptcha", method = RequestMethod.GET)
    public void getKaptcha(HttpServletResponse response/*, HttpSession session*/) {
        // 生成验证码
        String text = kaptchaProducer.createText();
        BufferedImage image = kaptchaProducer.createImage(text);
 
        // 将验证码存入session
        // session.setAttribute("kaptcha", text);
 
        // 验证码的归属
        String kaptchaOwner = CommunityUtil.generateUUID();
        Cookie cookie = new Cookie("kaptchaOwner", kaptchaOwner);
        cookie.setMaxAge(60);
        cookie.setPath(contextPath);
        response.addCookie(cookie);
        // 将验证码存入Redis
        String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
        redisTemplate.opsForValue().set(redisKey, text, 60, TimeUnit.SECONDS);
 
        // 将突图片输出给浏览器
        response.setContentType("image/png");
        try {
            OutputStream os = response.getOutputStream();
            ImageIO.write(image, "png", os);
        } catch (IOException e) {
            logger.error("响应验证码失败:" + e.getMessage());
        }
    }
 
    @RequestMapping(path = "/login", method = RequestMethod.POST)
    public String login(String username, String password, String code, boolean rememberme,
                        Model model, /*HttpSession session, */HttpServletResponse response,
                        @CookieValue("kaptchaOwner") String kaptchaOwner) {
        // 检查验证码
        // String kaptcha = (String) session.getAttribute("kaptcha");
        String kaptcha = null;
        if (StringUtils.isNotBlank(kaptchaOwner)) {
            String redisKey = RedisKeyUtil.getKaptchaKey(kaptchaOwner);
            kaptcha = (String) redisTemplate.opsForValue().get(redisKey);
        }
 
        if (StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)) {
            model.addAttribute("codeMsg", "验证码不正确!");
            return "/site/login";
        }
 
        // 检查账号,密码
        int expiredSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
        Map<String, Object> map = userService.login(username, password, expiredSeconds);
        if (map.containsKey("ticket")) {
            Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
            cookie.setPath(contextPath);
            cookie.setMaxAge(expiredSeconds);
            response.addCookie(cookie);
            return "redirect:/index";
        } else {
            model.addAttribute("usernameMsg", map.get("usernameMsg"));
            model.addAttribute("passwordMsg", map.get("passwordMsg"));
            return "/site/login";
        }
    }
 
    @RequestMapping(path = "/logout", method = RequestMethod.GET)
    public String logout(@CookieValue("ticket") String ticket) {
        userService.logout(ticket);
        SecurityContextHolder.clearContext();
        return "redirect:/login";
    }
 
}

这段代码也是很长,狗屎一样,我就简单说说这段代码大概的意思吧。

这段代码中使用了UserService来处理用户相关的业务逻辑,使用了Producer来生成验证码图片,以及使用了RedisTemplate来操作Redis数据库。

  1. /register和/login:这两个GET请求的处理方法返回注册页面和登录页面的视图。

  2. /register(POST请求):该方法处理用户注册的请求。根据用户提供的信息进行注册,并返回相应的结果。如果注册成功,会向用户的邮箱发送一封激活邮件。

  3. /activation/{userId}/{code}(GET请求):该方法处理用户账号激活的请求。通过传入的userId和code参数进行账号激活,并返回相应的结果。

  4. /kaptcha(GET请求):该方法生成验证码图片,并将验证码存入Redis和浏览器的Cookie中,以便后续验证。

  5. /login(POST请求):该方法处理用户登录的请求。首先检查验证码的正确性,然后根据用户提供的用户名和密码进行登录。如果登录成功,会生成一个ticket凭证,并将凭证存入浏览器的Cookie中,并重定向到首页。

  6. /logout(GET请求):该方法处理用户退出登录的请求。清除用户的登录凭证(ticket)并重定向到登录页面。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/415544.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

重拾前端基础知识:HTML

重拾前端基础知识&#xff1a;HTML 前言HTML编辑器标签锚链接 id和class表单Iframe字符实体HTML CSSHTML JavaScriptHTML 统一资源定位器(Uniform Resource Locators)XHTMLHTML5新的语义化元素视频和音频支持Canvas绘图本地存储表单增强Web Workers地理定位Web存储伸缩矢量图形…

《TCP/IP详解 卷一》第10章 UDP和IP分片

目录 10.1 引言 10.2 UDP 头部 10.3 UDP校验和 10.4 例子 10.5 UDP 和 IPv6 10.6 UDP-Lite 10.7 IP分片 10.7.1 例子&#xff1a;IPV4 UDP分片 10.7.2 重组超时 10.8 采用UDP的路径MTU发现 10.9 IP分片和ARP/ND之间的交互 10.10 最大UDP数据报长度 10.11 UDP服务器…

2024深度学习主流框架对比

tensorFlow 是最受欢迎和广泛使用的深度学习框架之一&#xff0c;目前在github的start数为181k。 TensorFlow是一个由Google Brain团队开发的开源深度学习框架。它允许开发者创建多种机器学习模型&#xff0c;包括卷积神经网络、循环神经网络和深度神经网络等&#xff0c;该框架…

全国产飞腾E2000Q +复旦微FPGA的轨道交通、电力解决方案

产品概述 ITX-XMF201是一款高性能边缘计算网关主板&#xff0c;采用飞腾E2000Q 4核处理器&#xff0c;国产化率达到95%国产化。 板载2电口&#xff0c;2路CAN&#xff0c;6路RS232接口&#xff0c;1路RS485接口&#xff0c;16路GPIO&#xff0c;可以满足银行、轨道交通、电力等…

【ArcGIS Pro二次开发】(83):ProWindow和WPF的一些技巧

在ArcGIS Pro二次开发中&#xff0c;SDK提供了一种工具界面【ArcGIS Pro ProWindow】。 关于ProWindow的用法&#xff0c;之前写过一篇基础的教程&#xff1a; 【ArcGIS Pro二次开发】(13)&#xff1a;ProWindow的用法_arcgispro二次开发教程-CSDN博客 主要是对几个常用控件…

Java开发的核心模式 - MVC

文章目录 1、MVC设计模式2、Web开发本质3、服务器的性能瓶颈 1、MVC设计模式 MVC设计模式示意图 在整个Java学习之旅中&#xff0c;MVC&#xff08;Model-View-Controller&#xff09;设计模式无疑占据着极其重要的地位&#xff0c;堪称理解和掌握Java项目开发精髓的钥匙。如…

【Python】Python实现串口通信(Python+Stm32)

&#x1f389;欢迎来到Python专栏~Python实现串口通信 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;Python学习专栏 文章作者技术和水平有限&#xff0c;如果文中出现错误&#xff0c;希望…

3D数字孪生

数字孪生&#xff08;Digital Twin&#xff09;是物理对象、流程或系统的虚拟复制品&#xff0c;用于监控、分析和优化现实世界的对应物。 这些数字孪生在制造、工程和城市规划等领域变得越来越重要&#xff0c;因为它们使我们能够在现实世界中实施改变之前模拟和测试不同的场景…

Spring 类型转换、数值绑定与验证(三)— Formatting 与 Validation

1 Formatting 在Spring中用于格式化数据及根据地域展示不同格式的数据。 图 Formatting接口 UML 1.1 注解驱动Formatting 自定义像“DateTimeFormat”注解来对相关字段格式化的步骤为&#xff1a; 自定义注解。定义一个实现AnnotationFormatterFactory接口的工厂类。往容器…

H3C OSPF Stub特殊区域+认证实验

H3C OSPF Stub特殊区域认证实验 实验拓扑 ​​ 实验需求 按照图示配置 IP 地址&#xff0c;所有路由器配置环回口 IP 地址为 X.X.X.X/32​ 作为 Router-id&#xff0c;X 为设备编号&#xff08;R5 除外&#xff09;按照图示分区域配置 OSPFR1 上配置默认路由&#xff0c;指…

数据卷dockerfile

目录 一、数据卷 1. 简介 2. 数据卷和数据卷容器 1. 数据卷&#xff1a; 2. 数据卷容器&#xff1a; 二、自定义镜像 1. 作用 2. 自定义centos 3. 自定义tomcat8 一、数据卷 1. 简介 数据卷是一个可供一个或多个容器使用的特殊目录&#xff0c;它将主机操作系统目录直…

小白水平理解面试经典题目leetcode. 606 Construct String from Binary Tree【递归算法】

Leetcode 606. 从二叉树构造字符串 题目描述 例子 小白做题 坐在自习室正在准备刷题的小白看到这道题&#xff0c;想想自己那可是没少和白月光做题呢&#xff0c;也不知道小美刷题刷到哪里了&#xff0c;这题怎么还没来问我&#xff0c;难道是王谦谦去做题了&#xff1f; 这…

使用Java和PostGis的全国A级风景区数据入库实战

目录 前言 一、数据介绍 1、空间数据 2、属性表说明 3、QGIS数据预览 二、PostGIS空间数据库设计 1、空间表结构 三、Java空间入库 1、实体定义 2、数据操作Mapper 3、业务层实现 4、入库 5、数据入库验证 总结 前言 星垂平野阔&#xff0c;月涌大江流”“晴川历历…

.NET生成MongoDB中的主键ObjectId

前言 因为很多场景下我们需要在创建MongoDB数据的时候提前生成好主键为了返回或者通过主键查询创建的业务&#xff0c;像EF中我们可以生成Guid来&#xff0c;本来想着要不要实现一套MongoDB中ObjectId的&#xff0c;结果发现网上各种各样的实现都有&#xff0c;不过好在阅读C#…

机器人内部传感器阅读梳理及心得-速度传感器-模拟式速度传感器

速度传感器是机器人内部传感器之一&#xff0c;是闭环控制系统中不可缺少的重要组成部分&#xff0c;它用来测量机器人关节的运动速度。可以进行速度测量的传感器很多&#xff0c;如进行位置测量的传感器大多可同时获得速度的信息。但是应用最广泛、能直接得到代表转速的电压且…

基于stm32F103的座面声控台灯

1.基本内容&#xff1a; 设计一个放置在桌面使用的台灯&#xff0c;使用220v交流电供电。具备显示屏能够实时显示日期&#xff08;年、月、日和星期&#xff09;&#xff0c;时间&#xff08;小时、分钟、秒&#xff09;和温度&#xff08;摄氏度&#xff09;&#xff1b;能够通…

高校物品捐赠管理系统|基于springboot高校物品捐赠管理系统设计与实现(源码+数据库+文档)

高校物品捐赠管理系统目录 目录 基于springboot高校物品捐赠管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户信息管理 2、捐赠信息管理 3、论坛信息管理 4、公告信息管理 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算…

回归预测 | Matlab实现CPO-BiTCN-BiGRU冠豪猪算法优化双向时间卷积门控循环单元多变量回归预测

回归预测 | Matlab实现CPO-BiTCN-BiGRU冠豪猪算法优化双向时间卷积门控循环单元多变量回归预测 目录 回归预测 | Matlab实现CPO-BiTCN-BiGRU冠豪猪算法优化双向时间卷积门控循环单元多变量回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现CPO-BiTCN-B…

比特币暴涨背后:通胀重现

作者&#xff1a;秦晋 这是一篇知名投资人安东尼庞普里亚诺&#xff08;Anthony Pompliano&#xff09;在2月27日写给投资者的一封信。 庞普里亚诺在信中将比特币暴涨归因于「通胀重现」。他表示&#xff0c;精明的投资者看到通胀将至&#xff0c;于是开始大手笔购买比特币。他…

【北京迅为】《iTOP-3588开发板网络环境配置手册》第2章 电脑、开发板直连交换机或路由器

RK3588是一款低功耗、高性能的处理器&#xff0c;适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用&#xff0c;RK3588支持8K视频编解码&#xff0c;内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…