java项目

  1. 项目大致分析

需要处理的数据的种类:用户,商品,收货地址,购物车,订单,收藏,商品分类……

开发时,应该先完成:比较简单的、与其它数据关联不大、是其它数据的基础支撑的数据,例如用户数据。

所以,处理数据的顺序可以大致是:用户 > 收货地址 > 商品分类与商品 > 购物车与收藏 > 订单。

每种数据的处理,都涉及一些功能,以用户数据为例,可能有:注册、登录、修改资料、修改密码、上传头像,通常,遵循“增 > 查 > 删 > 改”且“由简到难”的步骤一一完成,所以,当处理用户数据时,应该:注册 > 登录 > 修改密码 > 修改资料 > 上传头像。

在处理每一个功能时,都应该遵循的开发顺序:持久层 > 业务层 > 控制器层 > 界面。
2. 用户-注册-持久层

创建数据库

在MySQL中创建名为tedu_store的数据库:

CREATE DATABASE tedu_store;

创建数据表

CREATE TABLE t_user (
id INT AUTO_INCREMENT COMMENT ‘id’,
username VARCHAR(20) UNIQUE NOT NULL COMMENT ‘用户名’,
password CHAR(32) NOT NULL COMMENT ‘密码’,
salt CHAR(36) NOT NULL COMMENT ‘盐值’,
gender INT COMMENT ‘性别’,
phone VARCHAR(20) COMMENT ‘手机号码’,
email VARCHAR(50) COMMENT ‘电子邮箱’,
avatar VARCHAR(100) COMMENT ‘头像’,
is_delete INT DEFAULT 0 COMMENT ‘是否删除,0-未删除,1-已删除’,
created_user VARCHAR(20) COMMENT ‘创建者’,
created_time DATETIME COMMENT ‘创建时间’,
modified_user VARCHAR(20) COMMENT ‘最后修改者’,
modified_time DATETIME COMMENT ‘最后修改时间’,
PRIMARY KEY(id)
) DEFAULT CHARSET=UTF8;

创建项目

打开Spring Boot的官方网站:https://start.spring.io/

输入各参数,并勾选MyBatis和MySQL,生成项目。

将下载的压缩包解压到Workspace中,然后,在Eclipse中通过Import > Import > Existing Maven Projects导入该项目(如果是第1次使用Spring Boot,需要保证与Maven服务器的网络畅通)。

由于创建项目时勾选了数据库编程的依赖,所以,当Spring Boot启动时,会尝试创建数据源对象,但是,没有添加任何配置时,是无法创建数据源对象的,就会导致启动失败,所以,先在配置文件application.properties中添加数据源的配置:

spring datasource

spring.datasource.url=jdbc:mysql://localhost:3306/tedu_store?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root

框架会自动读取数据库驱动jar包中的META-INF/services/java.sql.Driver文件,文件中的值就是数据源的driverClassName的值,所以,配置数据源时,无须配置driverClassName。

完成后,测试连接是否可用:

package cn.tedu.store.db;

import java.sql.SQLException;

import javax.sql.DataSource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class DataSourceTestCase {

@Autowired
DataSource dataSource;

@Test
public void getConnection() throws SQLException {
    System.out.println(dataSource.getConnection());
}

}

创建实体类

创建实体类的基类cn.tedu.store.entity.BaseEntity,添加日志相关的4个属性,并实现序列化接口:

package cn.tedu.store.entity;

import java.io.Serializable;
import java.util.Date;

public class BaseEntity implements Serializable {

private static final long serialVersionUID = -6185124879935579311L;

private String createdUser;
private Date createdTime;
private String modifiedUser;
private Date modifiedTime;

public String getCreatedUser() {
    return createdUser;
}

public void setCreatedUser(String createdUser) {
    this.createdUser = createdUser;
}

public Date getCreatedTime() {
    return createdTime;
}

public void setCreatedTime(Date createdTime) {
    this.createdTime = createdTime;
}

public String getModifiedUser() {
    return modifiedUser;
}

public void setModifiedUser(String modifiedUser) {
    this.modifiedUser = modifiedUser;
}

public Date getModifiedTime() {
    return modifiedTime;
}

public void setModifiedTime(Date modifiedTime) {
    this.modifiedTime = modifiedTime;
}

}

创建cn.tedu.store.entity.User类,添加与数据表对应的属性,并继承自以上的BaseEntity类:

package cn.tedu.store.entity;

public class User extends BaseEntity {

private static final long serialVersionUID = -3643571432521920213L;

private Integer id;
private String username;
private String password;
private String salt;
private Integer gender;
private String phone;
private String email;
private String avatar;
private Integer isDelete;

public Integer getId() {
    return id;
}

public void setId(Integer id) {
    this.id = id;
}

public String getUsername() {
    return username;
}

public void setUsername(String username) {
    this.username = username;
}

public String getPassword() {
    return password;
}

public void setPassword(String password) {
    this.password = password;
}

public String getSalt() {
    return salt;
}

public void setSalt(String salt) {
    this.salt = salt;
}

public Integer getGender() {
    return gender;
}

public void setGender(Integer gender) {
    this.gender = gender;
}

public String getPhone() {
    return phone;
}

public void setPhone(String phone) {
    this.phone = phone;
}

public String getEmail() {
    return email;
}

public void setEmail(String email) {
    this.email = email;
}

public String getAvatar() {
    return avatar;
}

public void setAvatar(String avatar) {
    this.avatar = avatar;
}

public Integer getIsDelete() {
    return isDelete;
}

public void setIsDelete(Integer isDelete) {
    this.isDelete = isDelete;
}

@Override
public String toString() {
    return "User [id=" + id + ", username=" + username + ", password=" + password + ", salt=" + salt + ", gender="
            + gender + ", phone=" + phone + ", email=" + email + ", avatar=" + avatar + ", isDelete=" + isDelete
            + "]";
}

}

持久层接口

创建cn.tedu.store.mapper.UserMapper接口,并在接口中添加抽象方法:

package cn.tedu.store.mapper;

import cn.tedu.store.entity.User;

/**

  • 处理用户数据的持久层
    */
    public interface UserMapper {

    /**

    • 插入用户数据
    • @param user 用户数据
    • @return 受影响的行数
      */
      Integer addnew(User user);

    /**

    • 根据用户名查询用户数据
    • @param username 用户名
    • @return 匹配的用户数据,如果没有匹配的数据,则返回null
      */
      User findByUsername(String username);

}

由于注册时需要判断用户名是否被占用,所以,添加了根据用户名查询用户数据的方法。

为了使得MyBatis能找到接口,可以在启动类之前添加@MapperScan注解:

@SpringBootApplication
@MapperScan(“cn.tedu.store.mapper”)
public class TeduStoreApplication {

public static void main(String[] args) {
    SpringApplication.run(TeduStoreApplication.class, args);
}

}

当然,在接口文件之前添加@Mapper注解也可以,相比之下,使用@MapperScan是一次性配置,如果不使用它的话,每个接口文件之前都必须添加@Mapper注解,这2种方案选取其中的任何一种即可。

接下来,需要配置接口文件的映射,首先,在application.properties中添加XML文件位置的配置:

mybatis

mybatis.mapper-locations=classpath:mappers/*.xml

则需要在resources下创建名为mappers的文件夹,然后,在该文件夹下添加映射的XML,配置以上抽象方法的映射:

<?xml version="1.0" encoding="UTF-8" ?>
<!-- 插入用户数据 -->
<!-- Integer addnew(User user) -->
<insert id="addnew"
    parameterType="cn.tedu.store.entity.User"
    useGeneratedKeys="true"
    keyProperty="id">
    INSERT INTO t_user (
        username, password,
        salt, gender,
        phone, email,
        avatar, is_delete,
        created_user, created_time,
        modified_user, modified_time
    ) VALUES (
        #{username}, #{password},
        #{salt}, #{gender},
        #{phone}, #{email},
        #{avatar}, #{isDelete},
        #{createdUser}, #{createdTime},
        #{modifiedUser}, #{modifiedTime}
    )
</insert>

<!-- 根据用户名查询用户数据 -->
<!-- 应用于:注册,登录 -->
<!-- User findByUsername(String username) -->
<select id="findByUsername"
    resultType="cn.tedu.store.entity.User">
    SELECT 
        id, username, password,
        salt, avatar, 
        is_delete isDelete
    FROM 
        t_user
    WHERE 
        username=#{username}
</select>

当以上内容全部完成后,应该执行单元测试:

package cn.tedu.store.mapper;

import java.util.Date;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import cn.tedu.store.entity.User;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserMapperTestCase {

@Autowired
private UserMapper userMapper;

@Test
public void addnew() {
    Date now = new Date();
    User user = new User();
    user.setUsername("root");
    user.setPassword("1234");
    user.setGender(1);
    user.setPhone("13800138001");
    user.setEmail("root@tedu.cn");
    user.setSalt("Hello,MD5!");
    user.setIsDelete(0);
    user.setCreatedUser("Admin");
    user.setModifiedUser("Admin");
    user.setCreatedTime(now);
    user.setModifiedTime(now);
    Integer rows = userMapper.addnew(user);
    System.err.println("rows=" + rows);
}

@Test
public void findByUsername() {
    String username = "root";
    User user = userMapper.findByUsername(username);
    System.err.println(user);
}

}

  1. 用户-注册-业务层

业务层应该有业务层接口与实现类,允许被外部(控制器或其它业务类)调用的方法应该在接口中声明,例如reg()、login()方法等。

当前完成的是“注册”功能,则先创建业务层接口cn.tedu.store.service.IUserService:

/**

  • 处理用户数据的业务层接口
    */
    public interface IUserService {

}

已知注册过程中,可能出现“用户名被占用异常”、“插入数据异常”,这2种都是业务异常,则应该在cn.tedu.store.service.exception先创建对应的异常类:

ServiceException
DuplicateKeyException
InsertException

然后,在接口中声明“注册”的抽象方法:

/**

  • 用户注册

  • @param user 用户的注册信息

  • @return 成功注册的用户数据

  • @throws DuplicateKeyException

  • @throws InsertException
    */
    User reg(User user)
    throws DuplicateKeyException,
    InsertException;

    如果事先并不清楚可能抛出哪些异常,甚至还没有创建对应的异常类,可以直接编写业务方法的代码,后续再补充抛出异常的声明。

然后,创建cn.tedu.store.service.impl.UserServiceImpl类,并实现IUserService接口:

package cn.tedu.store.service.impl;

import cn.tedu.store.entity.User;
import cn.tedu.store.service.IUserService;
import cn.tedu.store.service.exception.DuplicateKeyException;
import cn.tedu.store.service.exception.InsertException;

public class UserServiceImpl
implements IUserService {

@Override
public User reg(User user) throws DuplicateKeyException, InsertException {
    // TODO Auto-generated method stub
    return null;
}

}

关于业务层的实现类,创建后,固定的任务有:

添加@Service注解;

声明@Autowired private UserMapper userMapper;持久层对象;

然后,添加与持久层对应的方法(甚至方法的声明几乎相同,如果是增删改方法,在业务层中,返回值可以调整为void,并通过异常表示操作失败),并声明为私有,通过调用持久层对象来完成方法:

/**

  • 插入用户数据
  • @param user 用户数据
  • @return 受影响的行数
  • @throws InsertException
    */
    private void addnew(User user) {
    Integer rows = userMapper.addnew(user);
    if (rows != 1) {
    throw new InsertException(
    “增加用户数据时出现未知错误!”);
    }
    }

/**

  • 根据用户名查询用户数据
  • @param username 用户名
  • @return 匹配的用户数据,如果没有匹配的数据,则返回null
    */
    private User findByUsername(String username) {
    return userMapper.findByUsername(username);
    }

然后,在公有的reg()方法中编写思路:

@Override
public User reg(User user) throws DuplicateKeyException, InsertException {
// 根据尝试注册的用户名查询用户数据
// 判断查询到的数据是否为null
// 是:用户名不存在,允许注册,则执行注册
// – 返回注册的用户对象
// 否:用户名已被占用,抛出DuplicateKeyException异常
return null;
}

然后,完成代码的编写:

@Override
public User reg(User user) throws DuplicateKeyException, InsertException {
// 根据尝试注册的用户名查询用户数据
User data = findByUsername(
user.getUsername());
// 判断查询到的数据是否为null
if (data == null) {
// 是:用户名不存在,允许注册,则执行注册
addnew(user);
// 返回注册的用户对象
return user;
} else {
// 否:用户名已被占用,抛出DuplicateKeyException异常
throw new DuplicateKeyException(
“注册失败!尝试注册的用户名(” + user.getUsername() + “)已经被占用!”);
}
}

虽然还有“加密”及其它细节没有处理,但是,可以先进行测试,所以,创建cn.tedu.store.service.UserServiceTestCase:

package cn.tedu.store.service;

import java.util.Date;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import cn.tedu.store.entity.User;
import cn.tedu.store.service.exception.ServiceException;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTestCase {

@Autowired
private IUserService userService;

@Test
public void reg() {
    try {
        Date now = new Date();
        User user = new User();
        user.setUsername("admin");
        user.setPassword("1234");
        user.setGender(1);
        user.setPhone("13800138002");
        user.setEmail("admin@tedu.cn");
        user.setSalt("Hello,MD5!");
        user.setIsDelete(0);
        user.setCreatedUser("Admin");
        user.setModifiedUser("Admin");
        user.setCreatedTime(now);
        user.setModifiedTime(now);
        User result = userService.reg(user);
        System.err.println("result=" + result);
    } catch (ServiceException e) {
        System.err.println("错误类型:" + e.getClass().getName());
        System.err.println("错误描述:" + e.getMessage());
    }
}

}

接下来,需要完成密码加密的功能,本次加密使用了随机的盐,可以使用UUID作为随机盐。

UUID是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成的API。按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字。

在Java中,通过java.util.UUID类即可轻松获取到随机的UUID值:

String uuid = UUID.randomUUID().toString();

在UserServiceImpl中添加加密方法:

/**

  • 获取根据MD5加密的密码
  • @param srcPassword 原密码
  • @param salt 盐值
  • @return 加密后的密码
    */
    private String getMd5Password(
    String srcPassword, String salt) {
    // 【注意】以下加密规则是自由设计的
    // ----------------------------
    // 盐值 拼接 原密码 拼接 盐值
    String str = salt + srcPassword + salt;
    // 循环执行10次摘要运算
    for (int i = 0; i < 10; i++) {
    str = DigestUtils
    .md5DigestAsHex(str.getBytes());
    }
    // 返回摘要结果
    return str;
    }

然后,在reg()方法中,在执行注册之前,先获取随机盐值,获取原始密码,执行加密,然后把盐值、加密后的密码封装回User对象中,再执行注册:

// 是:用户名不存在,允许注册,则处理密码加密
// 加密-1:获取随机的UUID作为盐值
String salt = UUID.randomUUID().toString();
// 加密-2:获取用户提交的原始密码
String srcPassword = user.getPassword();
// 加密-3:基于原始密码和盐值执行加密,获取通过MD5加密的密码
String md5Password = getMd5Password(srcPassword, salt);
// 加密-4:将加密后的密码封装在user对象中
user.setPassword(md5Password);
// 加密-5:将盐值封装在user对象中
user.setSalt(salt);
// 执行注册
addnew(user);

然后,在执行注册之前,还应该补充非用户提交的数据:

// 是否已经删除:否
user.setIsDelete(0);
// 4项日志
Date now = new Date();
user.setCreatedUser(user.getUsername());
user.setCreatedTime(now);
user.setModifiedUser(user.getUsername());
user.setModifiedTime(now);

至此,用户注册功能已经完成,由于前序产生的数据可能存在密码没有加密等问题,后续将无法正常使用,所以,应该清除t_user表中所有数据,后续需要数据时,重新注册即可。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值