UserCenter 项目疑问记录

如何进行企业级开发?其实不管做什么都需要事先做好规划.项目要实现的功能是什么,要怎么实现,做一个具体规划(预估时间),先完成框架搭建再去细化.

项目完整开发流程

  • 需求分析
  • 设计(概要设计、详细设计)
  • 技术选型(前端/后端)
  • 初始化(前端/后端),引入需要的技术
  • 搭建框架(Demo)【引入框架后可依据需求瘦身】
  • 实现业务逻辑
  • 测试(单元测试)
  • 代码提交/代码评审
  • 部署
  • 发布

Tips

  1. 框架瘦身阶段,每次删除一个文件都要重启项目是否能运行.
  2. 多查看官方文档.
  3. 养成好习惯,数据库相关操作语句做好本地或云端备份.

需要掌握的基本知识

controller:只需接收请求,不做任何业务处理.倾向对请求参数本身的校验,较少涉及业务逻辑校验.
service: 业务逻辑处理(校验)有可能被contoller之外的类调用
mapper(dao):数据访问层
model:数据封装类
utils:工具类(比如加密,日期转换,日期转换)
Lombook:帮助生成小的Java类实体对象

Spring Boot框架整合

  1. 创建数据库

    image-20250119104243355

  2. 建表
    image-20250119104354791

  3. 引入需要的依赖,比如Junit4(Maven Repository)

    1
    2
    3
    4
    5
    6
    7
    8
        <!-- JUnit -->
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
    </dependency>
    </dependencies>

4.配置yml文件.

1
2
3
4
5
6
7
8
9
10
11
spring:
application:
name: user-center
# DataSource Config
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/yourdatabase
username: yourname
password: yourpwd
server:
port: 8080

Mybatis-plus

新接触的技术文档学会去官网逐步学习.

Resource和Autowired区别

Resouce默认按照Bean对象名称进行注入,如果无法通过名称匹配则会按照类型注入.只能在Spring容器中使用.
Autowired默认按照类型注入如果有多个符合条件的Bean(多于一个匹配类型)可以结合@Qualifier注解按名称指定.

属性 @Resource @Autowired
name 支持,按名称注入 不支持
type 支持,按类型注入 不支持
required 不支持 支持,默认 required=true,可设置为 false

推荐使用Resource.

@Runwith注解

image-20250113211506790

1
@RunWith(SpringRunner.class)

使用@Runwith注解可解决注入对象为NULL问题

@Mapper和@MapperScan

image-20250113211801210

主配置文件中使用@MapperScan注解等价于在mapper接口(scan扫描定义的路径就是包含扫描到mapper包下所有内容)

image-20250113211842054

当然前提都要保证配置文件【pom.xml没问题】

Spring Boot 版本 MyBatis-Plus 版本 MyBatis 版本 MySQL 驱动版本 MySQL 数据库版本
3.1.x 3.5.x 3.5.x 8.0.33 - 8.0.34 5.7.x / 8.0.x
3.0.x 3.5.x 3.5.x 8.0.33 - 8.0.34 5.7.x / 8.0.x
2.7.x 3.5.x 3.5.x 8.0.30 - 8.0.33 5.7.x / 8.0.x
2.6.x 3.4.x - 3.5.x 3.4.x - 3.5.x 8.0.28 - 8.0.33 5.7.x / 8.0.x
2.5.x 3.4.x 3.4.x 8.0.28 - 8.0.31 5.7.x / 8.0.x
2.4.x 3.4.x 3.4.x 8.0.26 - 8.0.29 5.7.x / 8.0.x
2.3.x 3.3.x 3.3.x 8.0.22 - 8.0.27 5.7.x / 8.0.x

注意拉下来框架之后自己配置好MySQL对应依赖的content

1
2
3
4
5
6
7
8
9
<properties>
<java.version>1.8</java.version>
<spring-boot.version>2.6.13</spring-boot.version>
<mybatis-plus.version>3.5.9</mybatis-plus.version>
<mysql-connector.version>8.0.33</mysql-connector.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

数据表设计

特性 CHAR VARCHAR
长度 固定长度,无论实际内容多少 可变长度,仅存储实际内容
存储空间 占用固定空间,可能浪费存储 更节省空间,但有额外长度信息开销
性能 定长,性能更高 可变长度,性能略低
适用场景 长度固定的数据(如身份证号) 长度可变的数据(如用户名)
尾部空格处理 存储时补足空格,查询时忽略 存储时不 补足空格,查询时需精确匹配
最大长度 255 字符 65535 字节

多留余地万一以后要修改 varchar!

特性 INT TINYINT
存储空间 4 字节 1 字节
取值范围(有符号) -2,147,483,648 ~ 2,147,483,647 -128 ~ 127
取值范围(无符号) 0 ~ 4,294,967,295 0 ~ 255
适用场景 大范围数据(如主键、自增 ID) 小范围数据(如状态、布尔值)
性能 处理速度略低,存储空间更大 存储效率高,适合小范围高效存储

可以多使用tinyint 避免浪费空间.

什么时候需要索引

场景 是否设计索引
高频查询的列 ✅ 需要
作为主键或唯一性约束的列 ✅ 需要
经常排序、分组的列 ✅ 需要
大表中的查询条件列 ✅ 需要
小表、数据量很少的表 ❌ 不需要
写操作非常频繁的表 ❌ 避免过多索引
低选择性的列(重复值较多) ❌ 一般不需要
从不在查询条件中出现的列 ❌ 不需要

在实际设计中,应结合业务需求和数据分布情况,合理设计索引以平衡性能与存储开销。

DELETE 与 TRUNCATE 的差异总结

特性 DELETE TRUNCATE
类型 DML(数据操作语言) DDL(数据定义语言)
作用范围 可以带 WHERE 条件,删除部分或全部数据 删除表中所有数据,无法带条件
触发器 会触发触发器 不会触发触发器
日志记录 逐行记录删除操作,记录到事务日志中 不逐行记录,操作速度快
事务支持 支持事务,可以回滚 大多数数据库中不支持事务,操作不可回滚
性能 慢,逐行删除,记录日志 快,直接清空表
表结构 表结构、列、索引保留 表结构、列、索引保留
AUTO_INCREMENT 重置 不会重置 AUTO_INCREMENT 会重置 AUTO_INCREMENT
使用场景 删除部分数据或在事务中操作 清空表中所有数据,高效快速地删除大批量数据

id由数据库分配->auto

image-20250113222854939

自动生成器使用

MybatisX插件自动根据数据库生成domain实体对象、mapper(操作数据库的对象)、mapper.xml(定义了mapper对象和数据库的关联,可以在其中写自己的SQL)、service(包含常用的增删改查)、serviceImpl(实现具体service)

使用Mybatisx插件自动生成需要的类【右键表名】

image-20250114124902816

image-20250114125026113

根据Mybatisplus和Mybatis框架机制 会自动帮我们生成需要的Id(用户新增的ID会把塞到user表中)
image-20250114131948027

Mybatis-Plus默认转换机制

写了service的Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.yub.usercenter.service;
import java.util.Date;


import com.yub.usercenter.model.domain.User;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

/**
* 用户服务测试
* @author yub
*/
@SpringBootTest
public class UserServiceTest {

@Resource
private UserService userService;

@Test
public void tesAddUser() {
User user = new User();
user.setUsername("yub");
user.setUserAccount("123");
user.setAvatarUrl("https://tse3-mm.cn.bing.net/th/id/OIP-C.4RGfXFhVnIgc2CyGw-I9XAAAAA?w=208&h=208&c=7&r=0&o=5&dpr=1.3&pid=1.7");
user.setGender(1);
user.setUserPassword("xxx");
user.setPhone("123");
user.setEmail("456");
boolean result = userService.save(user);
System.out.println(user.getId());
Assertions.assertTrue(result);
}
}

出现报错 查看官方文档
image-20250114132807433

mapUnderscoreToCamelCase

  • 类型boolean
  • 默认值true

开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属性名 aColumn(驼峰命名) 的类似映射。

配置示例

1
2
3
mybatis-plus:
configuration:
map-underscore-to-camel-case: true

提示

在 MyBatis-Plus 中,此属性也将用于生成最终的 SQL 的 select body。如果您的数据库命名符合规则,无需使用 @TableField 注解指定数据库字段名。

Mybatis-Plus默认删除机制

MyBatis-Plus 的逻辑删除功能会在执行数据库操作时自动处理逻辑删除字段。以下是它的工作方式:

  • 插入:逻辑删除字段的值不受限制。
  • 查找:自动添加条件,过滤掉标记为已删除的记录。
  • 更新:防止更新已删除的记录。
  • 删除:将删除操作转换为更新操作,标记记录为已删除。

使用方法

步骤 1: 配置全局逻辑删除属性

application.yml 中配置 MyBatis-Plus 的全局逻辑删除属性:

1
2
3
4
5
6
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除字段名
logic-delete-value: 1 # 逻辑已删除值
logic-not-delete-value: 0 # 逻辑未删除值

步骤 2: 在实体类中使用 @TableLogic 注解

在实体类中,对应数据库表的逻辑删除字段上添加 @TableLogic 注解:

1
2
3
import com.baomidou.mybatisplus.annotation.TableLogic;
public class User { // 其他字段...
@TableLogic private Integer deleted;}

复杂重复的校验使用工具Apache Commons Lang
image-20250114135218842

用于校验用户账号是否出现特殊字符的正则表达式

1
2
//匹配 标点符号 (\pP)、符号字符 (\pS) 或 一个或多个空白字符 (\s+)。
"\\pP|\\pS|\\s+"

总结

  1. "\\pP|\\pS|\\s+" 使用 Unicode 属性匹配字符,比 [^a-zA-Z0-9_] 更加精准和丰富。
  2. 如果需要明确匹配标点符号、符号字符和空白,可以使用 "\\pP|\\pS|\\s+"
  3. 如果只需要简单排除非法字符(不属于某些范围的字符),可以使用 [^a-zA-Z0-9_]

自动装箱和拆箱

特性 自动装箱 自动拆箱
方向 基本数据类型 → 包装类 包装类 → 基本数据类型
触发场景 需要对象时,例如赋值给包装类变量或添加到集合中 需要基本类型时,例如参与运算或赋值给基本类型变量
主要方法实现 调用包装类的 valueOf() 方法 调用包装类的 xxxValue() 方法(如 intValue()
示例代码 Integer integer = 10; // 等价于 Integer.valueOf(10) int value = integer; // 等价于 integer.intValue()
作用 将基本数据类型变为对象,用于集合或对象需求场景 将对象转为基本类型,用于计算或基本类型需求场景
注意点 频繁装箱会有性能开销,可能导致产生大量对象 包装类是 null 时,会引发 NullPointerException
集合框架操作 List<Integer> list = new ArrayList<>(); int value = list.get(0);
对象赋值 Integer integer = 10; int num = integer;
算术运算 Integer a = 5; Integer b = 10; Integer sum = a + b; 在运算时,ab会拆箱为 int 类型并执行计算
方法调用 传递基本类型参数,包装类参数会自动装箱 方法返回包装类对象,接收者为基本类型会自动拆箱

image-20250114163625145

image-20250114165428279

小Tip
使用mapper注入思路更清晰,但功能不如直接使用ServiceImp继承全面.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService{

@Resource
private UserMapper userMapper;


//账号不能重复
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("userAccount", userAccount);
//这里的this其实就是UserServiceImpl拥有继承的父类的所有可继承的方法
// long count = this.count(queryWrapper);
//上一行是直接使用继承的父类的功能的等价于使用Usermapper注入
long count = userMapper.selectCount(queryWrapper);
if(count > 0) {
return -1;
}

请求参数很长的时候不建议用get【限流 获取IP 同一个IP登录过多次 封号】
校验 存储
使用JSON格式的话最好封装一个对象 单独储存参数
image-20250115110534408

大体积对象变成小体积(使用序列化),节省数据传输时间

测试

编写测试样例时,安装GenerateAllSetter插件可以快速生成默认值代码.
image-20250120220908304

IDEA自带的测试工具

image-20250115115048677

image-20250115115322123

session一天失效

特性 Session Cookie
存储位置 数据存储在服务器端,客户端只保存Session ID 数据存储在客户端浏览器中
生命周期 通常与会话相关,浏览器关闭或超时后失效 可以设置过期时间,也可以是会话级的
安全性 更安全,因为数据保存在服务器端,不易被篡改 较不安全,易受攻击(如XSS、劫持等),敏感信息不宜存储
数据类型 可以存储复杂的数据结构 只能存储字符串类型的数据
传输数据量 只传输Session ID,传输量小,性能较高 传输全部数据,可能增加网络开销
服务器负担 需要服务器资源来存储Session数据 不占用服务器资源,减轻服务器负担
适用场景 适用于需要处理敏感信息或复杂数据的场景 适用于存储客户端简单数据(如用户偏好)或跟踪用户行为

在application.yml文件下配置session

1
2
3
4
5
6
7
8
9
10
11
spring:
application:
name: user-center
# DataSource Config
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/yourdatabase
username: yourname
password: yourpwd
session:
timeout: 86400

通用类型data,使用泛型.不管接口返回的内容类型是什么都可以兼容.

1
2
3
4
5
6
7
8
9
10
11
12
package com.yub.usercenter.common;

import lombok.Data;

import java.io.Serializable;

@Data
public class BaseResponse<T> implements Serializable {
private int code;
private T data;
private String message;
}
类名 作用描述 主要功能
BaseResponse 通用返回类,用于封装API响应数据,包含状态码、数据、消息和描述等信息。 提供多种构造方法以适应不同的返回类型。
ErrorCode 错误码枚举类,定义了常见的错误类型及其对应的状态码、消息和描述。 管理和提供标准化的错误码信息。
ResultsUtils 返回工具类,提供便捷的方法创建标准化的响应对象,简化响应构造过程。 提供success和多种error方法用于生成响应。

BaseResponse

  • 用于统一定义接口返回的数据结构,包含状态码、数据、消息和描述等字段。

  • 提供了多种构造函数,支持不同情境下的响应创建(例如成功、错误等)。

  • ErrorCode 枚举类

  • 将常见的错误类型抽象为枚举,定义了每种错误的状态码、消息和描述。

  • 提供了一种集中式管理错误信息的方式,便于统一处理和维护。

ResultsUtils 工具类

  • 提供了简单的方法来快速生成BaseResponse对象,减少重复代码。
  • 包含针对成功和多种错误情况的静态方法,简化了对API响应的构建。

依据业务逻辑写的错误码

生成文档的话最好写上注释(Javadoc)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.yub.usercenter.common;

/**
* 错误码
* @author yub
*/
public enum ErrorCode {

PARAMS_ERROR(40000,"请求参数错误",""),//用户问题
NULL_ERROR(40001,"请求数据错误",""),
NOT_LOGIN(40100,"未登录",""),
NO_AUTH(40101,"没有权限","");

private final int code;
private final String message;
private final String description;

ErrorCode(int code, final String message, final String description) {
this.code = code;
this.message = message;
this.description = description;
}
}

枚举值不支持set,所以直接定义为final.
运行时异常不用throws捕获和try-catch捕获.可以理解为description是给前端用的(给前端返回参数)

定义业务异常类

  • 相对与Java的异常类,支持更多字段

  • 自定义构造函数,更灵活/快捷的设置字段

  • package com.yub.usercenter.exception;
    
    import com.yub.usercenter.common.ErrorCode;
    
    /**
     * 自定义异常类
     *
     * @author yub
     */
    public class BusinessException extends RuntimeException{
        private final int code;
        private final String description;
    
        public BusinessException(String message, int code, String description) {
            super(message);
            this.code = code;
            this.description = description;
        }
        public BusinessException(ErrorCode errorCode) {
            super(errorCode.getMessage());
            this.code = errorCode.getCode();
            this.description = errorCode.getDescription();
        }
        public BusinessException(ErrorCode errorCode, String description) {
            super(errorCode.getMessage());
            this.code = errorCode.getCode();
            this.description = description;
        }
    
        public int getCode() {
            return this.code;
        }
    
        public String getDescription() {
            return this.description;
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37

    **编写全局异常处理器**

    - 捕获代码中的所有异常,内部消化(封装),让前端得到更详细的业务报错/信息,同时屏蔽框架本身的异常(不暴露服务器内部状态)
    - 使用Spring AOP进行实现:在调用方法前后进行额外的处理
    - 集中处理,比如记录日志

    ```java
    package com.yub.usercenter.exception;

    import com.yub.usercenter.common.BaseResponse;
    import com.yub.usercenter.common.ErrorCode;
    import com.yub.usercenter.common.ResultsUtils;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;

    /**
    * 全局异常处理器
    *
    * @author yub
    */
    @RestControllerAdvice
    @Slf4j
    public class GolobalExceptionHandler {
    @ExceptionHandler(BusinessException.class)
    public BaseResponse businessExceotion(BusinessException e) {
    log.error("businessException:" + e.getMessage(),e);
    return ResultsUtils.error(e.getCode(),e.getMessage(),e.getDescription());
    }

    @ExceptionHandler(RuntimeException.class)
    public BaseResponse runtimeException(RuntimeException e) {
    log.error("runtimeException:",e);
    return ResultsUtils.error(ErrorCode.SYSTEM_ERROR,e.getMessage(),"");
    }
    }

自定义快捷键

image-20250120221223730

$END$是输入快捷语句之后能光标能自动定位到括号位置初.

image-20250120221257889

代理

  • 正向代理,替客户端向服务器接发送请求
  • 反向代理,替服务器接收请求.
    image-20250120214529168

application.yml指定接口全局api

1
2
servlet:
context-path: /api

多环境

  • 指同一套项目代码在不同的阶段需要根据实际情况来调整配置并且部署到不同的机器上.

为什么需要

  • 每个环境互不影响

  • 区分不同的阶段:开发/测试/生产

  • 对项目进行优化

    • 本地日志级别
    • 精简依赖,节约项目体积
    • 项目的环境/参数可以调整,比如JVM参数

    针对不同的环境做不同的事情.

多环境分类

  • 本地环境(自己的电脑)localhost
  • 开发环境(远程开发)多人协作
  • 测试环境(测试)开发/测试/产品。独立的数据库、独立的服务器
  • 预发布环境(体验服)基本和正式环境一致,正式环境的数据库更严谨查出问题
  • 正式环境(线上,公开访问的项目)尽量不改动,保证上线“完美”
  • 沙箱环境(实验环境)为了做实验

后端多环境实战

SpringBoot项目通过application.yml添加不同的后缀来区分.
image-20250118153224532

项目部署
前期准备
  1. 有一台自己的服务器(腾讯云阿里云等)
  2. 代码打包完成,完成部署备份文件.
使用Linux宝塔部署

充值宝塔访问端口和路径后无法正常访问image-20250119165712913

在腾讯云防火墙放行之后还要在Linux宝塔端同步放行.(踩大坑呜呜)

1
2
3
4
5
6
#检查已放行端口
firewall-cmd --zone=public --list-ports
#新增放行端口
firewall-cmd --zone=public --add-port=8080/tcp --permanent
#刷新
firewall-cmd --reload

总结

做好计划,从宏观到局部,由泛到精.