论坛系统(中-1)

软件开发

编写公共代码


定义状态码

对执⾏业务处理逻辑过程中可能出现的成功与失败状态做针对性描述(根据需求分析阶段可以遇见的问题提前做出定义),⽤枚举定义状态码,先定义⼀部分,业务中遇到新的问题再添加
定义状态码如下

状态码类型描述
0SUCCESS操作成功
1000FAILED操作失败
1001FAILED_UNAUTHORIZED未授权
1002FAILED_PARAMS_VALIDATE参数校验失败
1003FAILED_FORBIDDEN禁⽌访问
1004FAILED_CREATE新增失败
1005FAILED_NOT_EXISTS资源不存在
1101FAILED_USER_EXISTS⽤⼾已存在
1102FAILED_USER_NOT_EXISTS⽤⼾不存在
1103FAILED_LOGIN⽤⼾名或密码错误
1104FAILED_USER_BANNED您已被禁⾔, 请联系管理员, 并重新登录.
1105FAILED_TWO_PWD_NOT_S AME两次输⼊的密码不⼀致
2000ERROR_SERVICES两次输⼊的密码不⼀致
2001ERROR_IS_NULLIS NULL.

• 在 org.xiaobai.forum.common 包下创建枚举类型命名为ResultCode

这里有个很好用的东西,alt+鼠标左键,然后往下移动,就可以列编辑

具体的代码

package org.xiaobai.forum.common;public enum ResultCode {SUCCESS(0, "操作成功"),FAILED(1000, "操作失败"),FAILED_UNAUTHORIZED(1001, "未授权"),FAILED_PARAMS_VALIDATE(1002, "参数校验失败"),FAILED_FORBIDDEN(1003, "禁止访问"),FAILED_CREATE(1004, "新增失败"),FAILED_NOT_EXISTS(1005, "资源不存在"),FAILED_USER_EXISTS(1101, "用户已存在"),FAILED_USER_NOT_EXISTS(1102, "用户不存在"),FAILED_LOGIN(1103, "⽤⼾名或密码错误"),FAILED_USER_BANNED(1104, "您已被禁⾔, 请联系管理员, 并重新登录."),FAILED_TWO_PWD_NOT_SAME(1105, "两次输⼊的密码不⼀致"),ERROR_SERVICES(2000, "两次输⼊的密码不⼀致");int code;String message;// 构造方法ResultCode(int code, String message) {this.code = code;this.message = message;}@Overridepublic String toString() {return "code = " + code + ", message = " + message + ". ";}// get 和 set 方法public int getCode() {return code;}public void setCode(int code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}

定义返回结果

系统实现前后端分离,统⼀返回JSON格式的字符串,需要定义⼀个类,其中包含状态码,描述信息,返回的结果数据


• 在org.xiaobai.forum.forum.common包下创建AppResult类

具体代码, 此时还没规定传过来的数据是json

package org.xiaobai.forum.common;public class AppResult<T> {int code;String message;T data;// 提供构造方法public AppResult(int code, String message, T data) {this.code = code;this.message = message;this.data = data;}public AppResult(int code, String message) {this(code,message,null);}//对外提供静态方法方便调用//    成功public static AppResult success(){// 返回一个AppResult 对象return new AppResult(ResultCode.SUCCESS.getCode(),ResultCode.FAILED.getMessage());}// 成功自定义信息, 注册成功, 登录成功public static AppResult success(String message){return new AppResult(ResultCode.SUCCESS.getCode(),message);}// 成功自定义数据public static <T> AppResult<T> success(T data){return new AppResult<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(),data);}// 成功自定义信息和数据public static <T> AppResult<T> success(String message,T data){return new AppResult<>(ResultCode.SUCCESS.getCode(), message,data);}// 失败// 失败直接返回写死的的错误码和信息public static AppResult failed(){return new AppResult(ResultCode.FAILED.getCode() ,ResultCode.FAILED.getMessage());}// 失败返回写死的错误码和自定义信息public static AppResult failed(String message){return new AppResult(ResultCode.FAILED.getCode(),message);}// 失败返回ResultCodepublic static AppResult failed(ResultCode resultCode){return new AppResult(resultCode.getCode(),resultCode.getMessage());}// 生成get和set方法public int getCode() {return code;}public void setCode(int code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public T getData() {return data;}public void setData(T data) {this.data = data;}
}

注意:

java 枚举本质上是一个类的实例,因此可以像普通对象一样调用其方法和访问属性。

自定义异常 

创建⼀个异常类,加⼊状态码与状态描述属性

在org.xiaobai..forum.exception包下创建ApplicationException

主要的功能

自定义异常的代码 

package org.xiaobai.forum.exception;import org.xiaobai.forum.common.AppResult;
// 自定义异常
public class ApplicationException extends RuntimeException{// 自定义错误// 在异常中持有一个错误信息对象protected AppResult errorResult;public AppResult getErrorResult() {return errorResult;}
//     构造方法// 自己写的public ApplicationException (AppResult errorResult){// 传给父类// 打印ApplicationException, 而不是Runtime...super(errorResult.getMessage());this.errorResult = errorResult;}// 父类是RuntimeException, 我们重写它的方法public ApplicationException(String message) {super(message);}public ApplicationException(String message, Throwable cause) {super(message, cause);}public ApplicationException(Throwable cause) {super(cause);}
}

全局处理异常

使⽤@ControllerAdvice(类) + @ExceptionHandler(方法) 注解实现统⼀异常处理
补充: @ControllerAdvice 表⽰控制器通知类, @ExceptionHandler 是异常处理器,两个结合表⽰当出现异常的时候执⾏某个通知,也就是执⾏某个⽅法事件

在org.xiaobai.forum.exception包下创建GlobalExceptionHandler

接⼝返回为数据时, 需要加 @ResponseBody 注解

 具体代码

package org.xiaobai.forum.exception;import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.xiaobai.forum.common.AppResult;
import org.xiaobai.forum.common.ResultCode;// 全局异常处理
@Slf4j
@ControllerAdvice // 控制器通知类
public class GlobalExceptionHandler {//处理异常@ResponseBody@ExceptionHandler(ApplicationException.class)// 指定要处理的是哪个异常,异常处理器public AppResult applicationExceptionHandler(ApplicationException e) {// TODO 自定义的异常// 打印异常信息e.printStackTrace();// 生产的时候要删除// 打印日志log.error(e.getMessage());// 如果不为空就返回AppResultif (e.getErrorResult() != null) {return e.getErrorResult();}// e.getMessage()非空校验, 最保底的一个返回if (e.getMessage() == null || e.getMessage().equals("")) {// ERROR_SERVICES              (2000, "服务器内部错误"),return AppResult.failed(ResultCode.ERROR_SERVICES);}// 为空就返回具体的异常信息return AppResult.failed(e.getMessage());}// TODO 兜底的异常@ResponseBody@ExceptionHandler(Exception.class)//指定处理的是哪个异常public AppResult exceptionHandler(Exception e) {// 打印异常信息e.printStackTrace();// 打印日志log.error((e.getMessage()));// e.getMessage()非空校验, 最保底的一个返回if (e.getMessage() == null || e.getMessage().equals("")) {// ERROR_SERVICES              (2000, "服务器内部错误"),return AppResult.failed(ResultCode.ERROR_SERVICES);}// 返回异常信息return AppResult.failed(e.getMessage());}
}

测试异常处理

代码

package org.xiaobai.forum.controller;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.xiaobai.forum.common.AppResult;
import org.xiaobai.forum.exception.ApplicationException;//返回的是数据不是url
@RestController
@RequestMapping("/test")
public class TestController {@RequestMapping("/exception")public AppResult testException () throws Exception {throw  new Exception("这是一个Exception");}@RequestMapping("appException")public String testApplicationException(){throw new ApplicationException("这是一个自定义的ApplicationException");}
}

结果

 登录拦截器(现在先不实现)

实现API 自动生成

单个进行输入网址测试接口不利于管理, 十分的零散

使⽤Springfox Swagger⽣成API,并导⼊Postman,完成API单元测试

Swagger 简介:Swagger是⼀套API定义的规范,按照这套规范的要求去定义接⼝及接⼝相关信息,再通过可以解析这套规范⼯具,就可以⽣成各种格式的接⼝⽂档,以及在线接⼝调试⻚⾯,通过⾃动⽂档的⽅式,解决了接⼝⽂档更新不及时的问题

Springfox 简介:是对Swagger规范解析并⽣成⽂档的⼀个实现

Springfox ⽂档:Springfox Reference Documentation

 但是因为Springfox 很久每更新了,不支持spring boot 3.x 因此我们就使用另一个: Springdoc OpenAPI

Springfox Reference Documentation

swagger: 可以自动生成接口文档的东西,其他俩个是基于它来实现的

  • Swagger 是一个广泛的 API 文档规范和工具集,核心是 OpenAPI 规范
  • Springfox Swagger 是 Swagger 的一个实现,专为 Spring 项目生成 Swagger 2 规范的 API 文档。
  • Springdoc OpenAPI 是 Spring 的现代化库,专注于生成符合 OpenAPI 3.0 规范的 API 文档,逐渐取代了 Springfox。

使用: Springdoc OpenAPI 具体看这个博客: https://blog.csdn.net/2201_75880772/article/details/147875485?sharetype=blogdetail&sharerId=147875485&sharerefer=PC&sharesource=2201_75880772&sharefrom=mp_from_link

1> pom.xml来引入相关依赖

注意Spring Boot 版本不能太高我此时的版本是3.2.2

springdoc依赖

        <!-- 加入 springdoc 依赖 --><dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><version>2.5.0</version></dependency><repositories><!--阿里云镜像--><repository><id>alimaven</id><name>aliyun maven</name><url>https://maven.aliyun.com/nexus/content/groups/public/</url><releases><enabled>true</enabled></releases><snapshots><enabled>true</enabled></snapshots></repository>
</repositories>

2> 配置yml

server:
  port: 8080 (你项目的端口号)

springdoc:
  api-docs:
    enabled: true # 开启OpenApi接口
    path: /v3/api-docs  # 自定义路径,默认为 "/v3/api-docs"
  swagger-ui:
    enabled: true # 开启swagger界面,依赖OpenApi,需要OpenApi同时开启
    path: /swagger-ui/index.html # 自定义路径,默认为"/swagger-ui/index.html"

3> 配置swagger
在config 包下编写SwaggerConfig

package org.xiaobai.forum.config;import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** @Author HHHY* @ClassName* @Date: 2024/4/2 14:21* @Description: Swagger 配置*/@Configuration
public class SwaggerConfig {@Beanpublic OpenAPI springShopOpenAPI() {return new OpenAPI().info(new Info().title("Spring Boot 中使用 Swagger UI 构建 RESTful API").contact(new Contact()).description("百草中医药信息管理平台提供的 RESTful API").version("v1.0.0").license(new License().name("Apache 2.0").url("http://springdoc.org"))).externalDocs(new ExternalDocumentation().description("外部文档").url("https://springshop.wiki.github.org/docs"));}
}

4> 访问: http://localhost:8080/swagger-ui/swagger-ui/index.html#/ 

就有以下成功页面显示

如果觉得不行可以看看这个佬的博客:Spring Boot 3.x 引入springdoc-openapi (内置Swagger UI、webmvc-api)_springdoc-openapi-starter-webmvc-ui-CSDN博客

创建工具类(MD5加密工具类)

工具类类型

1> 注册和登录都会对用户密码进行加密, 因此可以对外提供一个加密的工具类

2> 生成随机字符串(盐值), 我们也要提供一个工具类

3> String类型做非空校验, 也要提供一个工具类

创建MD5加密工具类

1> pom.xml中导⼊依赖,SpringBoot已经对这个包做了版本管理,所以这⾥不⽤指定版本号

<!-- 编码解码加密⼯具包-->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>

2> 在 org.xiaobai.forum.utils包下创建MD5Util类

密码加密过程

1) 原密码

2) 生成扰动字符

3) 原密码(明文)进行MD5加密 => 密文1

4) 密文1 + 扰动字符 = 密文2

5) 对密文2 进行MD5加密

代码如下: 

package org.xiaobai.forum.utils;import org.apache.commons.codec.digest.DigestUtils;public class MD5Util {/*** @param str  明文* @return 密文*/public static String md5(String str){return DigestUtils.md5Hex(str);}/*** * @param str 密码明文* @param salt 扰动字符* @return 密文 */public static String md5Salt(String str, String salt){// ((明文)md5加密+扰动字符)md5加密return md5(md5(str)+salt);}}
生成UUID工具类

生成随机字符串(盐值) 36位字符

org.xiaobai.forum.utils包下创建UUIDUtil类 

package org.xiaobai.forum.utils;import java.util.UUID;public class StringUtil {/*** 生成一个36位的UUID* @return*/public static String UUID_36(){return UUID.randomUUID().toString();}/*** 生成一个32位的UUID* @return*/public static String UUID_32(){return UUID.randomUUID().toString().replace("-","");}}
创建字符串工具类

String类型做非空校验

org.xiaobai.forum.utils包下创建StringUtil类 

package org.xiaobai.forum.utils;public class StringUtil {/*** 判断字符串是为空* @param value* @return true 空 <br/> false 非空*/public static boolean isEmpty(String value){return value == null || value.length() == 0;}
}

实现业务功能

业务实现过程中主要的包和⽬录及主要功能:

• model 包:实体对象 根据数据库生成, 还有一些和业务相关的属性

• dao 包:数据库访问 调用mapper, 和数据库进行交互

• services 包:业务处理相关的接⼝与实现,所有业务都在Services中实现 1. 定义接口 2. 主要处理业务逻辑, 可能会涉及到事务操作, 遇到异常的时候, 统一抛出ApplicationException

• controller 包:提供URL映射,⽤来接收参数并做校验,调⽤Service中的业务代码,返回执⾏结果 用来接收请求, 包括参数, 并且对参数做校验, 然后传给service层进行业务处理, 然后进行返回

• src/main/resources/mapper ⽬录:Mybaits映射⽂件,配置数据库实体与类之间的映射关系

定义SQL

• src/main/resources/static ⽬录:前端资源

注册

顺序图

有⻚⾯跳转的流程我们提供了UML顺序图,后⾯逻辑相对简单的接⼝,我们在做之前只列出实现逻辑帮⼤家理清思路即可

参数要求

• 注册时需要⽤⼾提交的参数列表

前三个参数是和数据库交互要关注的参数

第四个参数是在Controller层就对密码和确认密码进行校验, 不通过就不传给service直接返回错误

参数名描述类型默认值条件
username⽤⼾名String必须
nickname昵称String与⽤⼾名相同必须
password密码String必须
passwordRepeat确认密码String必须,与密码相同
接口规范

// 请求
POST /user/register HTTP/1.1
Content-Type: application/x-www-form-urlencoded
username=user222&nickname=user222&password=123456&passwordRepeat=123456
// 响应
HTTP/1.1 200
Content-Type: application/json
{"code":0,"message":"成功","data":null}

注册功能实现后端代码
具体的实现步骤

实现过程:  Mapper -> Dao -> Service(进行单元测试) -> Controller(也需要进行单元测试)

1. 定义SQL, 按照用户名查询用户信息

2.  DAO 中对应类, 添加SQL中定义的方法

3. 进行Service中业务方法的编写

4. 进行单元测试

5. 编写Controller中的代码

6. 测试Controller中对外提供的API

1. 定义SQL, 按用户名查询用户信息

• src/main/resources/mapper⽬录下创建extension

• 由于src/main/resources/mapper ⽬录下是⾃动⽣成的映射⽂件,为防⽌后⾯修改数据再次⾃动成时把我们写的SQL给覆盖掉,新建⼀个扩展⽬录⽤来存放我们业务的SQL

• 在extension⽬录下新建 UserExtMapper.xml

a. 注意namespace表⽰命名空间,指定要与 UserMapper, xml中的namespace相同不同的映射⽂件指定了相同的namespace后,定义的所有⽤id或name标识的结果集映射都可以

b. 统⼀⽤org.xiaobai.forum.dao.UserMapper, 也就是UserMapper的完全限定名(包名+类名)

c. 不同的映射⽂件指定了相同的namespace后,定义的所有⽤id或name标识的结果集映射都可以不同⽂件中共享

• 代码如下:

<?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="org.xiaobai.forum.dao.UserMapper"><!--
1. 注意namespace表⽰命名空间,要与 UserMapper.xml中的namespace相同
2. 统⼀⽤org.xiaobai.forum.dao.UserMapper, 也就是UserMapper的完全限定名(包名+类名)
3. 不同的映射⽂件指定了相同的namespace后,定义的所有⽤id或name标识的结果集映射都可以
在不同的⽂件中共享
-->
<!--    根据用户名查询用户信息--><select id="selectByUserName" resultMap="BaseResultMap" parameterType="java.lang.String">select<include refid="Base_Column_List"></include>form t_userwheredeleteState = 0andusername =  #{username,jdbcType=VARCHAR}</select></mapper>
2.  DAO 中对应类, 添加SQL中定义的方法

•org.xiaobai.forum.dao包下的UserMapper中添加⽅法声明

• 注意⽅法名要与UserExtMapper.xml中SQL标签的id相同

@Param里面的参数是SQL的参数名, 后面的参数是java代码里面使用的变量名

3. 进行Service中业务方法的编写

•org.xiaobai.forum.service包下IUserService接⼝

实现接口

org.xiaobai.forum.service.impl包下创建UserServiceImpl类并实现IUserService接口,实现逻辑参考代码中的注释(impl下面是接口实现类)

具体的步骤:

1) 进行非空校验

2) 按用户名查询信息.(要判断用户是否存在)

3) 新增用户, 设置默认值

4) 新增用户, 写入数据库

具体代码:

package org.xiaobai.forum.service.impl;import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.xiaobai.forum.common.AppResult;
import org.xiaobai.forum.common.ResultCode;
import org.xiaobai.forum.dao.UserMapper;
import org.xiaobai.forum.exception.ApplicationException;
import org.xiaobai.forum.model.User;
import org.xiaobai.forum.service.IUserService;
import org.xiaobai.forum.utils.StringUtil;import java.util.Date;@Slf4j
@Service
public class UserServiceImpl implements IUserService {// 注入 mapper@Resourceprivate UserMapper userMapper;@Overridepublic void createNormalUser(User user) {// 1. 进行非空校验// 对非空的必填选项进行非空校验if(user == null || StringUtil.isEmpty(user.getUsername())|| StringUtil.isEmpty(user.getNickname())|| StringUtil.isEmpty(user.getPassword())|| StringUtil.isEmpty(user.getSalt())){// 打印日志: 参数校验失败log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());// 统一抛出 ApplicationException 自定义异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));}// 2. 按用户名查询用户信息User existsUser = userMapper.selectByUserName(user.getUsername());//1> 判断用户是否存在// 不存在说明是第一次注册if(existsUser!=null){// 打印日志log.info(ResultCode.FAILED_USER_EXISTS.toString());// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_USER_EXISTS));}// 3. 新增用户, 设置默认值user.setGender((byte)2);user.setArticleCount(0);user.setIsAdmin((byte)0);user.setState((byte)0);user.setDeleteState((byte)0);// 日期Date date = new Date();// 设置创建时间和修改时间user.setCreateTime(date);user.setUpdateTime(date);// 4. 新增用户,写入数据库int row = userMapper.insertSelective(user);// 插入失败if(row != 1){// 打印日志// 新增失败log.info(ResultCode.FAILED_CREATE.toString());// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_CREATE));}// 插入成功log.info("新增用户成功. username = "+user.getUsername()+". ");}
}
4. 进行单元测试

步骤

运行结果

5. 编写Controller中的代码

接收请求, 包括参数, 并且对参数做校验, 然后传给service层进行业务处理, 然后进行返回

org.xiaobai.forum.controller包下创建UserController类,实现逻辑参考代码中的注释

1> 对用户名, 昵称, 密码, 确认密码进行非空校验

2> 校验俩次输入的密码进行校验是否一致

3> 创建User, 对必传值进行设置

4> 对密码进行MD5加密处理.1) 生成盐. 2) 生成密码的密文(md5加密) 3) 设置密码和盐

5> 调用service 层, 把创建好的对象传过去

6> 返回成功

package org.xiaobai.forum.controller;import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.xiaobai.forum.common.AppResult;
import org.xiaobai.forum.common.ResultCode;
import org.xiaobai.forum.model.User;
import org.xiaobai.forum.service.impl.UserServiceImpl;
import org.xiaobai.forum.utils.MD5Util;
import org.xiaobai.forum.utils.UUIDUtil;// 对Controller进行API接口的描述
@Tag(name = "用户接口",description = "和用户相关的接口, 登录, 注册...")
// 日志注解
@Slf4j
// 这是一个返回数据的Controller
@RestController
// 路由映射, 一级路径
@RequestMapping("/user")
public class UserController {@ResourceUserServiceImpl userService;@Operation(summary = "用户注册")@PostMapping("/register") // 直接使用api注解来实现非空校验public AppResult register(@Parameter(description = "用户名")@RequestParam("username")@NonNull String username,@Parameter(description = "昵称")@RequestParam("nickname")@NonNull String nickname,@Parameter(description = "密码")@RequestParam("password")@NonNull String password,@Parameter(description = "确认密码")@RequestParam("passwordRepeat")@NonNull String passwordRepeat){// 校验密码和重复密码是否相同if(!password.equals(passwordRepeat)){// 俩次输入的密码不一致log.warn(ResultCode.FAILED_TWO_PWD_NOT_SAME.toString());// 返回错误信息return AppResult.failed(ResultCode.FAILED_TWO_PWD_NOT_SAME);}// 准备要插入的数据(必传值进行设置)User user = new User();user.setUsername(username);user.setNickname(nickname);// 处理密码// 1> 生成盐String salt = UUIDUtil.UUID_32();// 2> 生成密码的密文String encryptPassword = MD5Util.md5Salt(password,salt);// 3> 设置密码和盐user.setPassword(encryptPassword);user.setSalt(salt);// 调用Service层userService.createNormalUser(user);// 返回成功return AppResult.success();}
}

注意:  

关于非空校验

传统方式, 但是此时我用的是SpringDoc API里面的注解

关于SpringDoc API 的注解进行非空校验

6. 测试Controller中对外提供的API

我们此时测试成功, 其他情况有机会再测

注册功能实现前端代码
1> 前端技术介绍

HTML, CSS, JavaScript 最基础的前端技术

参考⽹站:https://developer.mozilla.org/zh-CN

jQuery  对原生JS进行了封装, 主要使用AJAX,DOM元素的操作相关方法

选择器

1) 标签选择器 $("div")

2) ID 选择器 $("#myDiv")

3) 类选择器 $(".myClass")

官⽹:https://jquery.com

HTML, CSS, JavaScript, jQuery相关中⽂资料⽹上有很多,可⾃⾏搜索

Bootstrap                 定义了HTML 元素的样式, 和页面布局

官⽹: https://getbootstrap.com Github: https://github.com/twbs 中⽂站:https://www.bootcss.com

Tabler                      对Bootstrap美化后的元素进行排版和组合, 形成一个个可以直接使用的组件, 比如: 表单, 列表, 图片墙...

Github: https://github.com/tabler/tabler

官⽹: https://tabler.io

图标                        丰富多彩的图标, 可以根据需要选择

https://tabler-icons.io/

toast                      用于提示信息的JS插件(类似于JAVA中的依赖, 提供一些好用的功能)

官⽹: https://kamranahmed.info/toast

2> 集成前端代码

把前端所有的资源放在src/main/resource/static 下(资源绑定自己下载)

测试: 进入http://127.0.0.1:8080/sign-up.html#

后续我们编写前端代码, 我们使用vscode作为开发工具

1)  导入相关的依赖 CSS

2) 引入JS

3) 处理业务逻辑

注意: jQuery提供的所有功能都是方法, 因此调用后必须加()

对用户名, 昵称,密码, 重复密码进参数校验

构造发送的数据

 构造ajax: 根据后端的响应结果来构造ajax

具体前端代码

<!doctype html><html lang="zh-CN"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" /><meta http-equiv="X-UA-Compatible" content="ie=edge" /><link rel="shortcut icon" href="/favicon.ico"><title>比特论坛 - 用户注册</title><!-- 导入CSS --><link href="./dist/css/tabler.min.css?1674944402" rel="stylesheet" /><link rel="stylesheet" href="./dist/css/jquery.toast.css"><!-- 设置字体 --><!-- <style>@import url('https://rsms.me/inter/inter.css');:root {--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;}body {font-feature-settings: "cv03", "cv04", "cv11";}</style> -->
</head><body class="d-flex flex-column"><!-- 正文 --><div class="page page-center"><div class="container container-tight py-4"><div class="text-center mb-4"><img src="./image/bit-forum-logo01.png" height="50" alt=""></div><form id="signUpForm" class="card card-md" autocomplete="off" novalidate><div class="card-body"><h2 class="text-center mb-4">用户注册</h2><!-- 用户名 --><div class="mb-3"><label class="form-label required">用户名</label><input type="text" class="form-control " placeholder="请输入用户名" name="username" id="username"><div class="invalid-feedback">用户名不能为空</div></div><!-- 昵称 --><div class="mb-3"><label class="form-label required">昵称</label><input type="text" class="form-control" placeholder="请输入昵称" name="nickname" id="nickname"><div class="invalid-feedback">昵称不能为空</div></div><!-- 密码 --><div class="mb-3"><label class="form-label required">密码</label><div class="input-group input-group-flat"><input type="password" class="form-control" placeholder="请输入密码" autocomplete="off" name="password"id="password"><span class="input-group-text"><a href="javascript:void(0);" class="link-secondary" id="password_a" title="显示密码"data-bs-toggle="tooltip"><!-- Download SVG icon from http://tabler-icons.io/i/eye --><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24"stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M12 12m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><pathd="M22 12c-2.667 4.667 -6 7 -10 7s-7.333 -2.333 -10 -7c2.667 -4.667 6 -7 10 -7s7.333 2.333 10 7" /></svg></a></span><div class="invalid-feedback">密码不能为空</div></div></div><!-- 确认密码 --><div class="mb-3"><label class="form-label required">确认密码</label><div class="input-group input-group-flat"><input type="password" class="form-control" placeholder="再次输入密码" autocomplete="off" name="passwordRepeat"id="passwordRepeat"><span class="input-group-text"><a href="javascript:void(0);" class="link-secondary" id="passwordRepeat_a" title="显示密码"data-bs-toggle="tooltip"><!-- Download SVG icon from http://tabler-icons.io/i/eye --><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24"stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none" /><path d="M12 12m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><pathd="M22 12c-2.667 4.667 -6 7 -10 7s-7.333 -2.333 -10 -7c2.667 -4.667 6 -7 10 -7s7.333 2.333 10 7" /></svg></a></span><div class="invalid-feedback">请检查确认密码</div></div></div><div class="mb-3"><label class="form-check"><input type="checkbox" class="form-check-input" id="policy" /><span class="form-check-label">同意 <a href="#" tabindex="-1">比特论坛使用条款和隐私政策</a>.</span></label></div><div class="form-footer"><button type="button" class="btn btn-primary w-100" id="submit">注册</button></div></div></form><div class="text-center text-muted mt-3">我已有一个账户? <a href="./sign-in.html" tabindex="-1">登录</a></div></div></div>
</body>
<!-- 导入JS -->
<script src="./dist/js/tabler.min.js"></script>
<script src="./dist/js/jquery-3.6.3.min.js"></script>
<script src="./dist/js/jquery.toast.js"></script>
<script>// 当前页加载成功后执行的方法$(function () {// 获取表单并校验$('#submit').click(function () {let checkForm = true;// 校验用户名if (!$('#username').val()) {$('#username').addClass('is-invalid');checkForm = false;}// 校验昵称if (!$('#nickname').val()) {$('#nickname').addClass('is-invalid');checkForm = false;}// 校验密码非空if (!$('#password').val()) {$('#password').addClass('is-invalid');checkForm = false;}// 校验确认密码非空, 校验密码与重复密码是否相同if (!$('#passwordRepeat').val() || $('#password').val() != $('#passwordRepeat').val()) {$('#passwordRepeat').addClass('is-invalid');checkForm = false;}// 检验政策是否勾选if (!$('#policy').prop('checked')) {$('#policy').addClass('is-invalid');checkForm = false;}// 根据判断结果提交表单if (!checkForm) {return;}// 构造数据let postData = {username: $('#username').val(),nickname: $('#nickname').val(),password: $('#password').val(),passwordRepeat: $('#passwordRepeat').val()};// 发送AJAX请求 // contentType = application/x-www-form-urlencoded// 成功后跳转到 sign-in.html$.ajax({type: 'post',url: 'user/register',contentType: 'application/x-www-form-urlencoded',data: postData,// 回调方法success: function (respData) {// 判断返回的状态码if (respData.code == 0) {// 跳转到登录页面location.assign('/sign-in.html');} else {// 提示信息$.toast({heading: '警告',text: respData.message,icon: 'warning'});}},error: function () {// 提示信息$.toast({heading: '错误',text: '访问出现问题,请与管理员联系.',icon: 'error'});}});});// 表单元单独检验$('#username, #nickname, #password').on('blur', function () {if ($(this).val()) {$(this).removeClass('is-invalid');$(this).addClass('is-valid');} else {$(this).removeClass('is-valid');$(this).addClass('is-invalid');}})// 检验确认密码$('#passwordRepeat').on('blur', function () {if ($(this).val() && $(this).val() == $('#password').val()) {$(this).removeClass('is-invalid');$(this).addClass('is-valid');} else {$(this).removeClass('is-valid');$(this).addClass('is-invalid');}})// 校验政策是否勾选$('#policy').on('change', function () {if ($(this).prop('checked')) {$(this).removeClass('is-invalid');$(this).addClass('is-valid');} else {$(this).removeClass('is-valid');$(this).addClass('is-invalid');}})// 密码框右侧明文密文切换按钮$('#passwordRepeat_a').click(function () {if ($('#passwordRepeat').attr('type') == 'password') {$('#passwordRepeat').attr('type', 'text');} else {$('#passwordRepeat').attr('type', 'password');}});$('#password_a').click(function () {if ($('#password').attr('type') == 'password') {$('#password').attr('type', 'text');} else {$('#password').attr('type', 'password');}});});</script></html>

登录

顺序图

参数要求
参数名描述类型条件
username⽤⼾名String必须
password密码String必须
接口规范

// 请求
POST /user/login HTTP/1.1
Content-Type: application/x-www-form-urlencoded
username=bitboy&password=123456
// 响应
HTTP/1.1 200
Content-Type: application/json
{"code":0,"message":"成功","data":null}

登录功能实现后端代码
1. 在Mapper.xml中编写SQL语句
2. 在Mapper.java中定义方法

1.2步在注册的时候已经写过了

3. 定义Service接口

定义一个login(username,password)

4. 实现Service接口

实现步骤

1) 对用户名和密码进行非空校验

2) 调用mapper层的根据用户名查询, 返回用户信息

3) 对查询到的用户进行非空校验(null 说明没查到)

4) 对密码进行MD5校验, 看经过MD5算法得到的密码和数据库的密码是否一致

5) 登录成功,打印日志

具体代码

  /*** @param username 用户名* @return 根据用户名来进行查询, 查询是否登录的用户在数据库存在, 存在久返回查询结果*/@Overridepublic User selectByUserName(String username) {// 非空校验if (StringUtil.isEmpty(username)) {// 打印日志// 参数校验失败log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));}// 调用mapper, 返回查询结果return userMapper.selectByUserName(username);}/*** 进行登录** @param username 用户名* @param password 密码* @return 登录成功返回登录用户*/@Overridepublic User login(String username, String password) {// 1. 非空校验if (StringUtil.isEmpty(username) || StringUtil.isEmpty(password)) {// 打印日志// 参数校验失败log.warn(ResultCode.FAILED_PARAMS_VALIDATE.toString());// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_PARAMS_VALIDATE));}// 2. 按用户名进行查询User user = selectByUserName(username);// 3. 对查询的结果进行非空校验if (user == null) {// 说明没有查到// 参数校验失败log.warn(ResultCode.FAILED_LOGIN.toString() + ", username" + username);// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_LOGIN));}// 4. 对密码进行非空校验: MD5(MD5(当前输入的密码)+随机字符(盐))String encryptPassword = MD5Util.md5Salt(password,user.getSalt());// 5. 用密文和数据库中存的用户密码进行比较if(!encryptPassword.equals(user.getPassword())){// 参数校验失败log.warn(ResultCode.FAILED_LOGIN.toString() + ", 密码错误, username" + username);// 抛出异常throw new ApplicationException(AppResult.failed(ResultCode.FAILED_LOGIN));}// 打印成功日志log.info("登录成功 username = " + username);// 登录成功 返回用户信息return user;}
5. 单元测试

编写测试代码,进行测试

注意: 使用@Transaction 会在测试后进行事务的回滚, 不会污染数据库中的数据

6. Controller实现方法并对外提供API接口

在UserController中实现登录⽅法

步骤

1) 使用注解进行非空校验

2) 调用Service中的登录方法, 返回User对象

3) 登录成功就把User对象设置到Session作用域中

4) 返回结果
具体代码

 /*** 用户登录* @param request 获取http请求, 设置session* @param username 用户名* @param password 密码* @return 返回登录成功/失败*/@Operation(summary = "用户登录")@PostMapping("/login") //1. 使用注解进行非空校验public AppResult login (HttpServletRequest request,@Parameter(description = "用户名") @RequestParam("username") @NonNull String username,@Parameter(description = "密码") @RequestParam("password") @NonNull String password){// 2. 调用Service中的登录方法, 返回User对象User user = userService.login(username,password);if(user == null){// 打印日志log.warn(ResultCode.FAILED_LOGIN.toString());// 返回结果return AppResult.failed(ResultCode.FAILED_LOGIN);}// 3. 如果登录成功就把User对象设置到Session作业域中HttpSession session = request.getSession(true);// 没有session就创建新的sessionsession.setAttribute(AppConfig.USER_SESSION,user);// 4. 返回结果return AppResult.success("登录成功");}

注意: 要设置为true , 这样没有session对象的时候就会新建一个

7. 测试API接口

启动项目, 进入这个url: http://localhost:8080/swagger-ui/swagger-ui/index.html#/

登录功能实现前端代码 

编写前端代码

1) 为相关的控件写一个id

2) 编写ajax请求

具体代码, 太多了只放ajax的

 // 构造数据let postData = {username : usernameEl.val(),password : passwordEl.val()}// 发送AJAX请求,成功后跳转到index.html$.ajax({type: 'post',url : 'user/login',contentType : 'application/x-www-form-urlencoded',data : postData,success : function (respData) {if (respData.code == 0) {location.assign('/index.html');} else {// 提示信息$.toast({heading: '警告',text: respData.message,icon: 'warning'});}},error : function () {// 提示信息$.toast({heading: '错误',text: '访问出现问题,请与管理员联系.',icon: 'error'});}});

进行测试: 

此时我只放一个错误的

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

E+H流量计通过Profibus DP主站转Modbus TCP网关与上位机轻松通讯

EH流量计通过Profibus DP主站转Modbus TCP网关与上位机轻松通讯 在现代工业自动化的广阔舞台上&#xff0c;Profibus DP与Modbus TCP这两种通信协议各领风骚&#xff0c;它们在不同的应用场景中发挥着举足轻重的作用。但工业生产的复杂性往往要求不同设备、系统之间能够顺畅沟…

服务器中存储空间不足该怎么办?

服务器作为存储数据信息的重要网络设备&#xff0c;随着企业业务的不断拓展&#xff0c;所需要存储的数据信息也在不断增加&#xff0c;最终会导致服务器中存储空间不足&#xff0c;这不仅会影响到服务器系统性能&#xff0c;还会造成业务无法正常执行&#xff0c;那么&#xf…

C++23 views::chunk_by (P2443R1) 详解

文章目录 引言C23 范围库概述范围视图&#xff08;Range Views&#xff09;范围算法&#xff08;Range Algorithms&#xff09;范围适配器&#xff08;Range Adapters&#xff09; std::views::chunk_by 介绍基本概念特性使用场景 示例代码简单示例自定义谓词示例 总结 引言 在…

零碳园区能源系统-多能互补体系

构建以可再生能源为核心的零碳园区能源系统&#xff0c;需整合光储直柔、光伏发电、微电网、氢能与储能技术&#xff0c;通过多能协同与智能调控实现能源生产、存储、消费全链条优化。以下是系统性实施方案&#xff1a; 一、系统架构设计 1. 多能互补体系 &#xff08;图示&a…

elastic search学习

首先在自己电脑上安装elastic search。安装成功后&#xff0c;查看ES是否启动成功。 安装过程参考&#xff1a;ElasticSearch入门1: mac 安装 - 霜井 - 博客园 安装完成后&#xff0c;直接执行bin目录中的elastic search命令后&#xff0c;就可以启动成功&#xff01; 在网页…

mysql8常用sql语句

查询结果带行号 -- 表名为 mi_user&#xff0c; 假设包含列 id &#xff0c;address SELECT ROW_NUMBER() OVER (ORDER BY id) AS row_num, t.id, t.address FROM mi_user t ; SELECT ROW_NUMBER() OVER ( ) AS row_num, t.id, t.address FROM mi_user t ; 更新某列数…

Memcached 服务搭建和集成使用的详细步骤示例

以下是 Memcached 服务搭建和集成使用的详细步骤示例&#xff1a; 一、搭建 Memcached 服务 安装 Memcached Linux 系统 yum 安装&#xff1a;执行命令 yum install -y memcached memcached-devel。源码安装 下载源码&#xff1a;wget http://www.memcached.org/files/memcach…

2. 盒模型/布局模块 - 响应式产品展示页_案例:电商产品网格布局

2. 盒模型/布局模块 - 响应式产品展示页 案例&#xff1a;电商产品网格布局 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><style type"text/css">:root {--primary-color…

Go基于plugin的热更新初体验

背景 对于一个部署在生产环境的项目来说&#xff0c;我们希望当代码出现bug的时候&#xff0c;可以不用重启进程而达到动态修改代码的目的—— 这就是代码热部署&#xff01; 使用java做游戏服务器&#xff0c;最大的好处是&#xff0c;当代码出现bug&#xff0c;可以直接热…

【RabbitMQ】工作队列和发布/订阅模式的具体实现

文章目录 建立连接工作队列模式实现创建队列和交换机生产者代码消费者代码运行程序启动消费者启动生产者 发布/订阅模式实现创建队列和交换机生产者代码创建交换机声明两个队列绑定队列和交换机发送消息完整代码 消费者代码完整代码 运行程序启动生产者启动消费者 建立连接 我…

Codeforces Round 998 (Div. 3)

A. Fibonacciness 题目大意 给你四个数字abde&#xff0c;让你找到一个中间值c&#xff0c;问 a b c a b c abc &#xff0c; b c d b c d bcd &#xff0c; c d e c d e cde 最多能有几个式子成立 解题思路 显然最多就六种情况&#xff0c;暴力枚举即可 代…

火山引擎发展初始

火山引擎是字节跳动旗下的云计算服务品牌&#xff0c;其云服务业务的启动和正式商业化时间线如下&#xff1a; 1. **初期探索&#xff08;2020年之前&#xff09;** 字节跳动在早期为支持自身业务&#xff08;如抖音、今日头条等&#xff09;构建了强大的基础设施和技术中…

【认知思维】光环效应:第一印象的持久力量

什么是光环效应 光环效应&#xff08;Halo Effect&#xff09;是指人们倾向于让对某人或某物的一个显著特质的印象影响对其他特质的评价的认知偏差。简单来说&#xff0c;当我们对某人的一个特质&#xff08;如外表、智力或某项技能&#xff09;形成积极印象时&#xff0c;我们…

Java Solon v3.3.0 发布(国产优秀应用开发基座)

Solon 框架&#xff01; Solon 是新一代&#xff0c;Java 企业级应用开发框架。从零开始构建&#xff08;No Java-EE&#xff09;&#xff0c;有灵活的接口规范与开放生态。采用商用友好的 Apache 2.0 开源协议&#xff0c;是“杭州无耳科技有限公司”开源的根级项目&#xff…

力扣-104.二叉树的最大深度

题目描述 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 class Solution { public:int maxDepth(TreeNode* root) {if(!root){return 0;}return max(maxDepth(root->left), maxDepth(root->…

单反和无反(私人笔记)

① 单反相机&#xff1a; 定义&#xff1a; 单反相机&#xff08;Single-Lens Reflex&#xff0c;SLR&#xff09;是一种带有反光镜结构的数码相机。光线通过镜头进入后&#xff0c;先被反光镜反射到五棱镜/五面镜&#xff0c;再通过取景器进入人眼。按下快门时&#xff0c;反…

超详细讲解C语言转义字符\a \b \r \t \? \n等等

转义字符 C语言有一组字符很特殊&#xff0c;叫做转义字符&#xff0c;顾名思义&#xff0c;改变原来的意思的字符。 1 \? ??)是一个三字母词&#xff0c;在以前的编译器它会被编译为] (??会被编译为[ 因此在以前输入(are you ok ??)就会被编译为are you ok ] 解决这个…

Java Spring MVC -01

SpringMVC 是一种基于 的实现 MVC 设计模式的请求驱动类型的轻量级 Web 框架&#xff0c;属于 Spring FrameWork 的后续产品&#xff0c;已经融合在 Spring Web Flow 中。 First:SpringMVC-01-SpringMVC 概述 SpringMVC 是 Spring 框架的一个模块&#xff0c;用于构建 Web 应…

Spring MessageSource 详解:如何在国际化消息中传递参数

在开发多语言应用程序时,Spring 的 MessageSource 是处理国际化(i18n)文本的核心组件。它允许我们根据用户的 Locale (区域设置) 显示不同的消息。然而,很多时候我们的消息并不是静态的,而是需要包含动态数据,比如用户名、数量、文件名等。这时,我们就需要在获取国际化消…

Datawhale 5月llm-universe 第1次笔记

课程地址&#xff1a;GitHub - datawhalechina/llm-universe: 本项目是一个面向小白开发者的大模型应用开发教程&#xff0c;在线阅读地址&#xff1a;https://datawhalechina.github.io/llm-universe/ 难点&#xff1a;配置conda环境变量 我用的vscode github方法 目录 重要…