论坛系统(中-1)
软件开发
编写公共代码
定义状态码
对执⾏业务处理逻辑过程中可能出现的成功与失败状态做针对性描述(根据需求分析阶段可以遇见的问题提前做出定义),⽤枚举定义状态码,先定义⼀部分,业务中遇到新的问题再添加
定义状态码如下
状态码 | 类型 | 描述 |
0 | SUCCESS | 操作成功 |
1000 | FAILED | 操作失败 |
1001 | FAILED_UNAUTHORIZED | 未授权 |
1002 | FAILED_PARAMS_VALIDATE | 参数校验失败 |
1003 | FAILED_FORBIDDEN | 禁⽌访问 |
1004 | FAILED_CREATE | 新增失败 |
1005 | FAILED_NOT_EXISTS | 资源不存在 |
1101 | FAILED_USER_EXISTS | ⽤⼾已存在 |
1102 | FAILED_USER_NOT_EXISTS | ⽤⼾不存在 |
1103 | FAILED_LOGIN | ⽤⼾名或密码错误 |
1104 | FAILED_USER_BANNED | 您已被禁⾔, 请联系管理员, 并重新登录. |
1105 | FAILED_TWO_PWD_NOT_S AME | 两次输⼊的密码不⼀致 |
2000 | ERROR_SERVICES | 两次输⼊的密码不⼀致 |
2001 | ERROR_IS_NULL | IS 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'});}});
进行测试:
此时我只放一个错误的
相关文章:

论坛系统(中-1)
软件开发 编写公共代码 定义状态码 对执⾏业务处理逻辑过程中可能出现的成功与失败状态做针对性描述(根据需求分析阶段可以遇见的问题提前做出定义),⽤枚举定义状态码,先定义⼀部分,业务中遇到新的问题再添加 定义状态码如下 状态码类型描…...

FPGA+ESP32 = GameBoy 是你的童年吗?
之前介绍的所有的复古游戏机都是基于Intel-Altera FPGA制作的,今天就带来一款基于AMD-Xilinx FPGA的复古掌上游戏机-Game Bub。 Game Bub是一款掌上游戏机,旨在畅玩 Game Boy、Game Boy Color 和 Game Boy Advance 游戏。与大多数现代掌上游戏机一样&…...

3D迷宫探险:伪3D渲染与运动控制的数学重构
目录 3D迷宫探险:伪3D渲染与运动控制的数学重构引言第一章 伪3D渲染引擎1.1 射线投射原理1.2 纹理透视校正第二章 迷宫生成算法2.1 图论生成模型2.2 复杂度控制第三章 第一人称控制3.1 运动微分方程3.2 鼠标视角控制第四章 碰撞检测优化4.1 层级检测体系4.2 滑动响应算法第五章…...

【金仓数据库征文】_金仓数据库在金融行业的两地三中心容灾架构实践
金仓数据库在金融行业的两地三中心容灾架构实践 🌟嗨,我是LucianaiB! 🌍 总有人间一两风,填我十万八千梦。 🚀 路漫漫其修远兮,吾将上下而求索。 引言 随着国家对信息技术应用创新࿰…...

Python作业练习3
任务简述 字符田字格绘制 代码实现 def print_tianzige():for i in range(11):if i in [0, 5, 10]:print("" "-----" * 2)else:print("|" " |" * 2)print_tianzige() 结果展示...

十五种光电器件综合对比——《器件手册--光电器件》
十五、光电器件 名称 原理 特点 应用 发光二极管(LED) 基于半导体材料的电致发光效应,当电流通过时,电子与空穴复合,释放出光子。 高效、节能、寿命长、响应速度快、体积小。 广泛用于指示灯、照明、显示&#…...

【计算机视觉】OpenCV项目实战:基于face_recognition库的实时人脸识别系统深度解析
基于face_recognition库的实时人脸识别系统深度解析 1. 项目概述2. 技术原理与算法设计2.1 人脸检测模块2.2 特征编码2.3 相似度计算 3. 实战部署指南3.1 环境配置3.2 数据准备3.3 实时识别流程 4. 常见问题与解决方案4.1 dlib安装失败4.2 人脸检测性能差4.3 误识别率高 5. 关键…...
Python面向对象编程(OOP)深度解析:从封装到继承的多维度实践
引言 面向对象编程(Object-Oriented Programming, OOP)是Python开发中的核心范式,其三大特性——封装、继承、多态——为构建模块化、可维护的代码提供了坚实基础。本文将通过代码实例与理论结合的方式,系统解析Python OOP的实现机制与高级特性…...
自我奖励语言模型:突破人类反馈瓶颈
核心思想 自我奖励语言模型提出了一种全新的语言模型对齐范式。传统方法如RLHF或DPO依赖人类反馈数据训练固定的奖励模型,这使模型的能力受限于人类标注数据的质量和数量。论文作者认为,要实现超人类能力的AI代理,未来的模型需要突破人类反馈…...

游戏资源传输服务器
目录 项目简介项目实现nginx配置服务器逻辑图 项目代码简介reactor 模型部分文件传输部分 项目演示视频演示演示分析 项目简介 使用C开发,其中资源存储在fastdfs 中,用户通过http上传或下载资源文件,此项目需要开启nginx中的nginx-upload-mod…...

2025-5-13渗透测试:CVE-2021-42278 和日志分析,NTLM 协议和PTH (Pass-the-Hash) Relay 捕获 Hash
CVE-2021-42278/42287 漏洞利用 漏洞原理 42278:通过修改计算机账户的 sAMAccountName(如去掉 $),伪装成域控制器(DC)名称,欺骗KDC生成高权限TGT。42287:KDC在验证TGT时若找不到匹配…...

基于深度学习的水果识别系统设计
一、选择YOLOv5s模型 YOLOv5:YOLOv5 是一个轻量级的目标检测模型,它在 YOLOv4 的基础上进行了进一步优化,使其在保持较高检测精度的同时,具有更快的推理速度。YOLOv5 的网络结构更加灵活,可以根据不同的需求选择不同大…...

C——五子棋小游戏
前言 五子棋,又称连珠棋,是一种双人对弈的棋类游戏。游戏目标是在一个棋盘上,通过在横、竖、斜线上依次放置棋子,使自己的五个棋子连成一线,即横线、竖线或斜线,且无被对手堵住的空位,从而获胜…...

【线段树】P9349 [JOI 2023 Final] Stone Arranging 2|普及+
本文涉及知识点 C线段树 P9349 [JOI 2023 Final] Stone Arranging 2 题目描述 JOI-kun has N N N go stones. The stones are numbered from 1 1 1 to N N N. The color of each stone is an integer between 1 1 1 and 1 0 9 10^9 109, inclusive. In the beginning,…...
分别在windows和linux上使用curl,有啥区别?
作为开发者常用的网络工具,curl 在 Windows 和 Linux 上的使用看似相似,但实际存在不少细节差异。以下从 命令语法、环境特性、功能支持 和 开发体验 四个角度展开对比,帮助读者避免跨平台开发时的常见“坑”。 一、命令语法差异:…...

CodeBuddy终极测评:中国版Cursor的开发革命(含安装指南+HTML游戏实战)
一、腾讯云CodeBuddy产品全景解读 1. 什么是腾讯云代码助手? 官方定义: Tencent Cloud CodeBuddy是由腾讯自研的AI编程辅助工具,基于混元大模型DeepSeek双引擎,提供: ✅ 智能代码补全(支持200语言&#x…...

从数据中台到数据飞轮:实现数据驱动的升级之路
从数据中台到数据飞轮:实现数据驱动的升级之路 随着数字化转型的推进,数据已经成为企业最重要的资产之一,企业普遍搭建了数据中台,用于整合、管理和共享数据;然而,近年来,数据中台的风潮逐渐减退…...
机器学习第八讲:向量/矩阵 → 数据表格的数学表达,如Excel表格转数字阵列
机器学习第八讲:向量/矩阵 → 数据表格的数学表达,如Excel表格转数字阵列 资料取自《零基础学机器学习》。 查看总目录:学习大纲 关于DeepSeek本地部署指南可以看下我之前写的文章:DeepSeek R1本地与线上满血版部署:…...

8天Python从入门到精通【itheima】-1~5
目录 1节: 1.Python的优势: 2.Python的独具优势的特点: 2节-初识Python: 1.Python的起源 2.Python广泛的适用面: 3节-什么是编程语言: 1.编程语言的作用: 2.编程语言的好处:…...

T2000云腾边缘计算盒子在数猪场景中的应用|YOLOv8+NodeRED
在现代养猪业蓬勃发展的当下,养殖场的智能化管理成为提升效率与精准度的关键所在。而养猪场盘点工作一直是养殖场管理中的重要环节,传统的盘点方式不仅耗费大量人力、时间,还容易出现误差。如今,T2000 云腾边缘计算盒子的出现&…...

Baklib内容中台构建全攻略
内容中台构建路径全解析 企业构建内容中台需遵循“战略驱动-系统搭建-持续优化”的三阶段路径。首先明确业务目标与知识资产类型,通过显性知识结构化将分散内容转化为标准化数字资产,依托四库体系(知识库、资源库、模板库、规则库࿰…...
Spark的缓存
RDD缓存 Spark速度非常快的原因之一,就是在不同操作中可以在内存中持久化或缓存多个数据集。当持久化某个RDD后,每一个节点都将把计算的分片结果保存在内存中,并在对此RDD或衍生出的RDD进行的其他动作中重用。这使得后续的动作变得更加迅速。…...

爬虫工具与编程语言选择指南
有人问爬虫如何选择工具和编程语言。根据我多年的经验来说,是我肯定得先分析不同场景下适合的工具和语言。 如果大家不知道其他语言,比如JavaScript(Node.js)或者Go,这些在特定情况下可能更合适。比如,如果…...

系统平衡与企业挑战
在复杂的系统中,一切都在寻找平衡,而这个平衡从不静止。它在不断的变化与反馈中调整,以适应外界环境的变动。就像一个企业,它无法完全回避变化,但却总是在挑战中寻找新的平衡点。 最近遇到一家企业,引入了…...

征程 6 yolov5s-rgb-nhwc 量化指南
在 征程 6 平台,我们可以按照这个方式编译 input_typr_rt 为 rgb,且 layout 为 NHWC 的模型。这样做的好处是,当用户的数据输入源本身就是 NHWC 的 rgb 图像时,这么做可以避免额外的数据处理操作。这里以 yolov5s 为例进行介绍。 …...

国产化Word处理控件Spire.Doc教程:如何使用 C# 从 Word 中提取图片
通过编程方式从 Word 文档中提取图片,可以用于自动化文档处理任务。E-iceblue旗下Spire系列产品是国产文档处理领域的优秀产品,支持国产化,帮助企业高效构建文档处理的应用程序。本文将演示如何使用 C# 和 Spire.Doc for .NET 库从 Word 文件…...

Telnet 类图解析
Telnet 类图(文本描述) --------------------------------------- | Telnet | --------------------------------------- | - host: str | # 目标主机 | - port: int …...
Python之with语句
文章目录 Python中的with语句详解一、基本语法二、工作原理三、文件操作中的with语句1. 基本用法2. 同时打开多个文件 四、with语句的优势五、自定义上下文管理器1. 基于类的实现2. 使用contextlib模块 六、常见应用场景七、注意事项 Python中的with语句详解 with语句是Python…...
【Flask全栈开发指南】从零构建企业级Web应用
目录 🌟 前言🏗️ 技术背景与价值🚧 当前技术痛点🛠️ 解决方案概述👥 目标读者说明 🔍 一、技术原理剖析📊 核心概念图解💡 核心作用讲解🧩 关键技术模块说明⚖️ 技术选…...
mac 10.15.7 svn安装
macOS 版本推荐 SVN 安装方式≤10.14Homebrew 安装独立 SVN≥10.15优先使用 CLT 自带 SVN 一、使用 brew 安装 (没成功) brew install subversion 这个方法安装一直不成功,一直在提示说版本旧或都是一些引用工具安装失败, 二、使…...