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

2024-02-29 11:12

本文主要是介绍仿牛客网项目---用户注册登录功能的实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

我们做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方法@Overridepublic 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;  //与登录凭证相关联的用户的IDprivate String ticket;  //登录凭证的字符串值private int status;   //登录凭证的状态,表示凭证的有效性或失效状态private Date expired;  //登录凭证的过期时间,表示凭证的有效期限// 省略了getter和setter方法@Overridepublic 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/codeString 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() {@Overridepublic 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);// 将验证码存入RedisString 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)并重定向到登录页面。

这篇关于仿牛客网项目---用户注册登录功能的实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/758576

相关文章

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount