[从零开始的账本项目01] 用户模块

272 阅读5分钟

虽然是第一个模块, 但是暂时没有直接开始进行核心功能, 也就是账本的功能的编码.
选择了先对用户的相关功能进行了编码.

选定了功能模块后, 粗浅分析一下编码的方向.

User 类

对于一名用户来讲, 所具有的属性:

image.png

  • id : 此处选择了Mybatis Plus作为了操作数据库的框架
  • username : 用户名, 暂定设定usernamemax_length15
  • password : 密码, 暂时先使用明文存储
  • phone : 手机号, 能够作为用户的登录时的用户名
  • email : 邮箱, 同样能够作为用户的登录时的用户名
  • profile : 头像, 暂定选用阿里云的OSS进行头像的上传存储, 创建用户时, 给定默认头像url
  • gender : 性别, 设定0为女, 1为男
  • birthday : 出生日期, 类型为 java.sql.Date
  • registerTime : 注册日期, 当创建用户时, 自动进行设置.
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@TableName("bngel_user")
public class User {
    public static Integer MAX_LENGTH_OF_USERNAME = 15;
    public static Integer OVER_LENGTH_ERROR_CODE = 410;
    public static String OVER_LENGTH_ERROR_MESSAGE = "用户名长度越界";
    public static String UNKNOWN_USER_MESSAGE = "查询用户不存在";
    public static String LOGIN_ERROR_MESSAGE = "用户名或密码错误";

    private Long id;
    private String username;
    private String password;
    private String phone;
    private String email;
    private String profile;
    private Integer gender;
    private Date birthday;
    private Date registerDate;
}

由于技术选型使用的是Mybatis Plus, 因此直接创建对应Mapper
在自己的Mapper中继承BaseMapper, 泛型中填入对应需要使用的数据类.

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

使用@Mapper注解进行注入, 对应的功能就可以直接在接下来的Service类中进行封装使用.

UserService 类

其实MyBatis Plus提供了封装好的IService供我们使用, 但是还是想自己写一个对应的Service,因此没有选用

public interface UserService{

    Integer saveUser(User user);

    Integer deleteUserById(Long id);

    Integer updateUser(User user);

    User getUserById(Long id);

    Integer updateUsernameById(Long id, String username);

    User login(String account, String password);
}

对其进行一个impl的实现

@Service
public class UserServiceImpl implements UserService{

    @Autowired
    private UserMapper userMapper;

    @Override
    public Integer saveUser(User user) {
        return userMapper.insert(user);
    }

    @Override
    public Integer deleteUserById(Long id) {
        return userMapper.deleteById(id);
    }

    @Override
    public Integer updateUser(User user) {
        return userMapper.updateById(user);
    }

    @Override
    public User getUserById(Long id) {
        return userMapper.selectById(id);
    }

    @Override
    public User login(String account, String password) {
        return userMapper.login(account, password);
    }

    @Override
    public Integer updateUsernameById(Long id, String username) {
        User user = new User();
        user.setId(id);
        user.setUsername(username);
        return userMapper.updateById(user);
    }
}

通过对mapper@Autowire的直接注入, 能够在service中之间使用对应的方法.
其中内置了90%的一般方法供我们使用.
只需要进行一层封装即可.

UserController 类

接下来就是更为细化的Controller类, 将已有的接口暴露给用户进行使用.
比方说update方法, 用户也许只想修改其中的一个属性,
因此可以对其进行细化处理. 例如updateUsernameupdatePhone
但是在这之前, 需要有一个返回数据时通用的CommonResult类. 用于用户访问接口后数据的返回.

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class CommonResult<T> {

    public static Integer SUCCESS_CODE = 200;
    public static Integer FAILURE_CODE = 400;
    public static String SUCCESS_MESSAGE = "success";
    public static String FAILURE_MESSAGE = "failed";

    private Integer code;
    private T data;
    private String message;

    public CommonResult(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public static CommonResult commonSuccessResult() {
        return new CommonResult(SUCCESS_CODE, SUCCESS_MESSAGE);
    }

    public static CommonResult commonFailureResult() {
        return new CommonResult(FAILURE_CODE, FAILURE_MESSAGE);
    }
}

通过泛型直接传入我们需要返回的数据
事先封装了两种通用的返回方式(成功、失败)

@RestController
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/user/save")
    public CommonResult saveUser(@RequestBody User user) {
        user.setRegisterDate(new Date(new java.util.Date().getTime()));
        Integer result = userService.saveUser(user);
        if (result == 1) {
            log.info("创建用户: [" + user + "]");
            return CommonResult.commonSuccessResult();
        } else {
            log.info("创建用户: [" + user + "] 失败");
            return CommonResult.commonFailureResult();
        }
    }

    @DeleteMapping("/user")
    public CommonResult deleteUser(@RequestParam("id") Long id) {
        Integer result = userService.deleteUserById(id);
        if (result == 1) {
            log.info("删除用户: [" + id + "]");
            return CommonResult.commonSuccessResult();
        } else {
            log.info("删除用户: [" + id + "] 失败");
            return CommonResult.commonFailureResult();
        }
    }

    @PutMapping("/user/update/username")
    public CommonResult updateUsernameById(@RequestParam("id") Long id,
                                           @RequestParam("username") String username) {
        if (username.length() > User.MAX_LENGTH_OF_USERNAME) {
            return new CommonResult(User.OVER_LENGTH_ERROR_CODE, User.OVER_LENGTH_ERROR_MESSAGE);
        }
        Integer result = userService.updateUsernameById(id, username);
        if (result == 1) {
            log.info("更改用户名[" + id + "]: " + username);
            return CommonResult.commonSuccessResult();
        } else {
            log.info("更改用户名[" + id + "]: 失败");
            return CommonResult.commonFailureResult();
        }
    }

    @GetMapping("/user")
    public CommonResult getUserById(@RequestParam("id") Long id) {
        User user = userService.getUserById(id);
        if (user != null) {
            log.info("查询用户: [" + user + "]");
            return new CommonResult(CommonResult.SUCCESS_CODE, user, CommonResult.SUCCESS_MESSAGE);
        }
        else {
            log.info("查询用户[" + id + "]: 失败");
            return new CommonResult(CommonResult.FAILURE_CODE, User.UNKNOWN_USER_MESSAGE);
        }
    }

    @PostMapping("/user/login")
    public CommonResult login(@RequestParam("account") String account,
                              @RequestParam("password") String password) {
        User user = userService.login(account, password);
        if (user != null) {
            log.info("用户登录: [" + user + "]");
            return new CommonResult(CommonResult.SUCCESS_CODE, user, CommonResult.SUCCESS_MESSAGE);
        }
        else {
            log.info("登录失败[" + account + "]: 失败");
            return new CommonResult(CommonResult.SUCCESS_CODE, User.LOGIN_ERROR_MESSAGE);
        }
    }
}

添加了适当的友好的log信息.

  • Controller类中, 添加了log方便维护
  • 通过sql语句, 指定了登录的方式为手机号邮箱.

到了这里, 用户模块还算是告了一段落.
但是还有需要补充的, 决定放到后期进行实现.

  • 通过手机号发送验证码直接进行用户的注册/登录.
  • 修改手机号需要判断是否已存在该手机号已绑定的用户且需要发送验证码
  • 修改邮箱地址需要判断是否该邮箱已被绑定且需要发送验证码
  • 头像的上传因为还没有上云, 所以决定后期再补全
  • 注册/登录时需要对密码进行一定的加密处理

最后将我们的模块注册到我们的nacos

# bootstrap.yml
server:
  port: 8001

spring:
  application:
    name: bngelbook-user-provider
  jackson:
    date-format: yyyy-MM-dd
  servlet:
    multipart:
      max-file-size: 20MB
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
      config:
        server-addr: localhost:8848
        file-extension: yaml

考虑到之后也许还有相同的服务实现分布式. 因此为了减少配置的重复. 就把相关的datasource配置放到了nacos配置中心中, 动态调取 通过本地的application.yml设置环境

spring:
  profiles:
    active: dev

在配置中心的bngelbook-user-provider-dev.yaml如下所示

spring:
    datasource:
        url: jdbc:mysql://localhost:3306/bngelbook?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver

Mysql8.0之后就需要配置serverTimezone=UTC

最后的最后. 给我们的接口加上接口文档, 这里的话我选用了knife4j. 因为考虑到之后的模块也需要使用该组件, 因此我把它放在了父项目的dependencies

<dependency>
  <groupId>com.github.xiaoymin</groupId>
  <artifactId>knife4j-spring-boot-starter</artifactId>
  <version>2.0.9</version>
</dependency>

这里使用3.x版本的话, 经测试, 使用与官网给出的quick start有点出入, 于是选用了2.x版本中的最新版

添加接口文档的过程比较冗杂且公式化, 就省略了. 可参见github的代码.


欢迎各位来对我的小项目提出各种宝贵的意见, 这对我的进步非常重要, 谢谢大家.
GitHub地址: Bngel/bngelbook