历史记录
Web项目实战 | 购物系统v2.0 | 开发记录(一)需求分析 | 技术选型 | 系统设计 | 数据表设计 | SpringBoot、SSM、Thymeleaf、Bootstrap…
Web项目实战 | 购物系统v2.0 | 开发记录(二)
一、运行环境
- windows10
- IDEA 2021.1 专业版
- JDK8
- SpringBoot2
- Druid 1.2.5
- Bootstrap 4.6.0
- MySQL 8
- Navicat 11
二、环境准备
创建 SpringBoot项目,导入相关的起步依赖、配置Druid数据源,测试数据库连接
2.1 使用 IDEA 创建SpringBoot项目
第一步,新建项目
第二步,选择需要引入的依赖,这里不选全也没事,本质上就是在pom.xml添加相关starter起步依赖,后续还可以添加其他的起步依赖 第三步,了解项目结构 SpringBoot的配置文件都必须命名为application,不过它具有三种不同的格式,有properties、yaml、yml,这里笔者使用yml格式,因为看起来整洁许多,如果同时都有配置的话,是有一个优先级的,在同一级目录下优先级(高 -> 低)为 properties -> yml -> yaml [相关内容的笔记链接]
2.2 导入所需依赖
除了之前选择的Web、Thymeleafr、Lombok以外,还要确保有mysql、autoconfigure等依赖,具体配置如下: pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/>
</parent>
<groupId>com.uni</groupId>
<artifactId>simple-shop-system-v2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>simple-shop-system-v2</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>4.6.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.3 配置Druid数据源
在pom.xml里添加相同依赖: pom.xml
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.5</version>
</dependency>
application.yml
spring:
datasource:
username: root
password: 数据库密码
url: jdbc:mysql://localhost:3306/simple_shop_system_2?useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
2.4 编写Java类配置Druid
其实在导入Druid起步依赖后,它会有默认的配置注入到Spring容器的,其原理就是通过Java类进行配置,观察jar包里的源码内容:
其源码是通过注解进行配置的,而我们在配置时只需要@Configuration注解表示当前类是一个配置类就行,通过源码可以看到,其作者(来自阿里的大佬)留下了他的QQ号~
在配置Druid时需注意,需要配置哪个对象就使用该对象定义一个方法,同时用@Bean注解标记该方法,这样SpringBoot可以将方法返回的结果注入到Spring容器,即完成配置
DruidConfig.java
package com.uni.config;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DruidConfig {
@Bean
public ServletRegistrationBean statViewServlet(){
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
Map<String,String> initParams = new HashMap<>();
initParams.put("loginUsername","admin");
initParams.put("loginPassword","123456");
initParams.put("allow","127.0.0.1");
initParams.put("dz","192.168.1.66");
bean.setInitParameters(initParams);
return bean;
}
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
Map<String,String> initParams = new HashMap<>();
initParams.put("exclusions","*.js,*.css,/druid/*,/jdbc/*");
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
2.5 测试Druid
方式一: 启动SpringBoot项目,访问Druid后台
最简单粗暴的方法就是直接启动项目,访问Druid后台,地址为:https://localhost:8080/duird/login.html ,登陆账号和密码就是之前Config类里配置的
方式二: 使用Junit单元测试编写测试方法,DataSource 对象是从Spring容器获取的,因为SpringBoot会将它注入到容器中,最后运行没报错就行!
@Autowired
DataSource dataSource;
@Test
void testDruid() throws SQLException {
System.out.println(dataSource.getClass());
Connection connection = dataSource.getConnection();
System.out.println(connection);
DruidDataSource druidDataSource = (DruidDataSource) dataSource;
System.out.println("druidDataSource 数据源最大连接数"+druidDataSource.getMaxActive());
System.out.println("druidDataSource 数据源初始化连接数"+druidDataSource.getInitialSize());
connection.close();
}
三、MyBatis 逆向工程
根据上次的笔记,MyBatis逆向工程,本次项目开发可以使用这个功能,而无需每次都重新写CRUD,实现逆向工程的方法有多种,常见的有XML配置 + Maven插件 ,XML 配置 + Java类反向代理,这里笔者选择Maven插件。
SpringBoot整合MyBatis逆向工程
3.1 导入所需依赖
pom.xml
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.6</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
</dependencies>
<configuration>
<configurationFile>src/main/resources/mybatis/generatorConfig.xml</configurationFile>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
</plugin>
</plugins>
</build>
3.2 逆向工程配置
generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="MySqlTables" defaultModelType="flat"
targetRuntime="MyBatis3">
<property name="beginningDelimiter" value="`" />
<property name="endingDelimiter" value="`" />
<property name="javaFileEncoding" value="UTF-8" />
<property name="javaFormatter"
value="org.mybatis.generator.api.dom.DefaultJavaFormatter" />
<property name="xmlFormatter"
value="org.mybatis.generator.api.dom.DefaultXmlFormatter" />
<plugin type="org.mybatis.generator.plugins.SerializablePlugin" />
<commentGenerator>
<property name="suppressDate" value="true" />
<property name="suppressAllComments" value="true" />
</commentGenerator>
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/simple_shop_system_2?characterEncoding=UTF-8"
userId="root"
password="数据库密码">
</jdbcConnection>
<javaTypeResolver
type="org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl">
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<javaModelGenerator targetPackage="com.uni.pojo"
targetProject="src/main/java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<sqlMapGenerator targetPackage="mybatis.mapper"
targetProject="src/main/resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.uni.dao" targetProject="src/main/java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<table tableName="uni_commodity" domainObjectName="Commodity">
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="uni_commoditytype" domainObjectName="CommodityType">
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="uni_order" domainObjectName="Order">
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="uni_provider" domainObjectName="Provider">
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="uni_role" domainObjectName="Role">
<property name="useActualColumnNames" value="true"/>
</table>
<table tableName="uni_user" domainObjectName="User">
<property name="useActualColumnNames" value="true"/>
</table>
</context>
</generatorConfiguration>
3.3 运行Maven插件启动逆向工程
3.4 问题与优化
由逆向工程生成的Mapper结果提供了许多CRUD的方法,比如在用户接口里 UserMapper.java
package com.uni.dao;
import com.uni.pojo.User;
import com.uni.pojo.UserExample;
import java.util.List;
import org.apache.ibatis.annotations.Param;
public interface UserMapper {
long countByExample(UserExample example);
int deleteByExample(UserExample example);
int deleteByPrimaryKey(Long id);
int insert(User record);
int insertSelective(User record);
List<User> selectByExample(UserExample example);
User selectByPrimaryKey(Long id);
int updateByExampleSelective(@Param("record") User record, @Param("example") UserExample example);
int updateByExample(@Param("record") User record, @Param("example") UserExample example);
int updateByPrimaryKeySelective(User record);
int updateByPrimaryKey(User record);
}
}
上述是默认生成的Mapper接口类,在使用时只需要声明接口,并使用`@Autowired从Spring的IoC容器获取对应的Bean就可以调用相关方法,由于是第二次使用,关于逆向工程还有一些潜在的小问题,现进行一个总结:
问题1: 自动生成的pojo实体类没有toString ()方法
- 解决方式:使用lombok的@Data注解自动生成toString方法,同时删去不必要的setter、getter方法,因为lombok可以自动生成这样的方法。不过有些set、get是对字段有特殊处理的,就不需要删,比如修改后的用户pojo类如下:
User.java
package com.uni.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
@Data
@NoArgsConstructor
public class User implements Serializable {
private Long id;
private String userCode;
private String userName;
private String userPassword;
private Integer gender;
private Date birthday;
private String phone;
private String address;
private Long createBy;
private Date createionDate;
private Long modifyBy;
private Date modifyDate;
private String pwd;
private String salt;
private Integer userRole;
private String userImgSrc;
private static final long serialVersionUID = 1L;
public void setUserCode(String userCode) {
this.userCode = userCode == null ? null : userCode.trim();
}
public void setUserName(String userName) {
this.userName = userName == null ? null : userName.trim();
}
public void setUserPassword(String userPassword) {
this.userPassword = userPassword == null ? null : userPassword.trim();
}
public void setPhone(String phone) {
this.phone = phone == null ? null : phone.trim();
}
public void setAddress(String address) {
this.address = address == null ? null : address.trim();
}
public void setPwd(String pwd) {
this.pwd = pwd == null ? null : pwd.trim();
}
public void setSalt(String salt) {
this.salt = salt == null ? null : salt.trim();
}
public void setUserImgSrc(String userImgSrc) {
this.userImgSrc = userImgSrc == null ? null : userImgSrc.trim();
}
}
问题2: SpringBoot如何整合这些Mapper接口
- 解决方法:和之前Spring整合MyBatis类似,先进行DAO层接口扫描的配置,这样就能通过注解@Component将DAO层的类注入到Spring容器。
(1)MyBatis配置文件和SQL映射文件的配置 application.yml
mybatis:
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
(2)配置支持扫描Mapper接口的包,这个在SpringBoot启动类中配置,使用@MapperScan(value = "包名") 注解即可
package com.uni;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(value = "com.uni.dao")
public class SimpleShopSystemV2Application {
public static void main(String[] args) {
SpringApplication.run(SimpleShopSystemV2Application.class, args);
}
}
(3)在Mapper接口类里使用注解@Component 注入到Spring容器 问题3: 如何测试逆向工程的Mapper接口
- 解决方法:按照之前MVC的设计结构,Mapper接口应当在Service层使用,现在通过Junit单元测试进行简单的测试,看看是否能从Spring容器中获取的Bean是否能正常执行相关方法
@Autowired
RoleMapper roleMapper;
@Test
public void TestMyBatisGenerator(){
roleMapper.selectByExample(new RoleExample()).forEach(
role -> System.out.println(role)
);
}
运行结果如下: 至此,MyBatis逆向工程 、项目的MyBatis环境设置完毕
四、实现登陆页面
通过Bootstrap4框架实现,验证码使用了现成的接口,接下来会详细说明
4.1 导入所需依赖
pom.xml
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>4.6.0</version>
</dependency>
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>bootstrap-icons</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.3.1</version>
</dependency>
4.2 实现代码
HTML部分代码:这里是通过Thymeleaf引擎渲染页面,负责前后端交互,是SpringBoot官方支持的引擎,通过各种th标签可以获取到Controller层传输的数据。这里导入资源的方式是webjar, 即之前导入的Jar包依赖,是通过Maven工程的GAV坐标定位资源文件的。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>登录页面</title>
<link th:href="@{/webjars/bootstrap/4.6.0/css/bootstrap.css}" rel="stylesheet">
<link th:href="@{/webjars/bootstrap-icons/1.7.2/font/bootstrap-icons.css}" rel="stylesheet">
</head>
<body>
<script type="text/javascript" th:src="@{/webjars/jquery/3.3.1/jquery.js}"></script>
</body>
4.3 判断用户是否存在+获取用户头像
为了更友好的提示用户,当用户离开账号的输入框时候,输入框会做出反馈,若存在则成呈现为绿色,否则就呈现为红色,这个可以通过 Boostrap4框架提供的CSS类来实现,即 is-valid 和 is-invalid
<script>
var check_usercode = true
var check_password = true
$("#form-usercode").blur(function (){
let userCode = $(this).val()
if (userCode != '') {
$.ajax({
url: '/login/' + userCode,
type: 'GET',
async: false,
error: (err) => {
alert('AJAX请求失败!')
console.log(err.status)
console.log(err.responseText)
},
success: (result) => {
if (result == 'true') {
check_usercode = true
$("#form-usercode").removeClass('is-invalid')
$("#form-usercode").addClass('is-valid')
} else {
check_usercode = false
$("#form-usercode").removeClass('is-valid')
$("#form-usercode").addClass('is-invalid')
}
}
})
} else {
check_usercode = false
$("#form-usercode").removeClass('is-valid')
$("#form-usercode").addClass('is-invalid')
}
if(check_usercode){
$.ajax({
url: '/imgSrc/' + userCode,
type: 'GET',
async: false,
dataType: 'text',
error: (err) => {
alert('查找身份AJAX请求失败!')
console.log(err.responseText)
},
success: (imgSrc) => {
$('.card-img-top').attr('src', imgSrc)
}
})
} else $('.card-img-top').attr('src', '/images/user_common.png')
});
$("#btn-login").click({
})
</script>
Controller层: UserAction.java
@Autowired
private UserService userService;
@GetMapping("/login/{userCode}")
@ResponseBody
public String checkUserCode(@PathVariable String userCode){
return userService.checkUserCode(userCode) ? "true" : "false";
}
@GetMapping("/imgSrc")
@ResponseBody
public String defaultImgSrc(){
return "/images/user_common.png";
}
@GetMapping("/imgSrc/{userCode}")
@ResponseBody
public String selectImgSrc(@PathVariable String userCode){
String imgSrc = userService.getImgSrc(userCode);
return imgSrc;
}
Service层
@Autowired
UserMapper userMapper;
@Override
public Boolean checkUserCode(String userCode) {
UserExample example = new UserExample();
UserExample.Criteria criteria = example.createCriteria();
criteria.andUserCodeEqualTo(userCode);
return userMapper.selectByExample(example).size() == 1;
}
@Override
public String getImgSrc(String userCode) {
List<User> users = userMapper.selectByExample(new UserExample() {{
Criteria criteria = createCriteria();
criteria.andUserCodeEqualTo(userCode);
}});
return users != null && users.size() == 1? users.get(0).getUserImgSrc() : null;
}
4.4 通过类配置web首页
通过实现WebMvcConfigurer接口,再通过@Configuration注入到Spring容器这种方式可以得SpringMVC进行配置,比如可以将 "/"的请求转发到地址为"login"的视图,后缀默认为html
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
}
}
4.5 图片验证码接口 Captcha
作者:Hansin1997
项目地址:https://gitee.com/hansin/captcha(已star)
笔者是根据这位大神提供的开发文档实现的图片验证码,总共有以下四步。
第一步,导入依赖 pom.xml
<dependency>
<groupId>cn.dustlight.captcha</groupId>
<artifactId>captcha-core</artifactId>
<version>1.0.1</version>
</dependency>
第二步,在SpringBoot启动类中使用注解@EnableCaptcha 对captcha 使能
package com.uni;
import cn.dustlight.captcha.annotations.EnableCaptcha;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(value = "com.uni.dao")
@EnableCaptcha
public class SimpleShopSystemV2Application {
public static void main(String[] args) {
SpringApplication.run(SimpleShopSystemV2Application.class, args);
}
}
第三步,在Controller层编写业务逻辑
package com.uni.controller;
import cn.dustlight.captcha.annotations.CodeValue;
import cn.dustlight.captcha.annotations.SendCode;
import cn.dustlight.captcha.annotations.VerifyCode;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CodeAction {
@RequestMapping("/captcha")
@SendCode
public void captcha(@CodeValue String code) {
return ;
}
@RequestMapping("/code")
@VerifyCode
public String index(@CodeValue String code) {
return String.format("Hello World! (%s)", code);
}
}
第四步,在HTML中的图片里指定src到Controller层对应的地址
<img th:src="@{/captcha}" style="width: 60px; height: 34px">
五、Bug & DeBug
使用MyBatis逆向工程后,User表识别错误,识别到了MySQL自带的User用户表
解决:修改表名,所有表加上前缀uni_,然后重新启动Maven插件,逆向生成DAO层接口和SQL映射文件
MyBatsi逆向工程生成的POJO类属性全是小写的,在配置时需在table标签里添加<property name="useActualColumnNames" value="true"/> ,用于指定当前表遵循驼峰命名规则
Thymeleaf图片没有正常显示
<img th:src="@{/static/images/user_common.png}" class="img-circle" width="100px" height="100px">
解决:去掉/static
六、总结
今天花时间最多的还是在前端设计上,看来很有必要整一套适合自己的页面设计,不然每次都花时间去调样式,太不合理,通过本次开发,更加熟悉了SpringBoot的注解配置,其作用和XML是类似的,需要配置的时候查资料就行,其次是MyBatis逆向工程真的很方便,今天虽然还没真正实现登陆的需求,但是能感觉到根本不需要写SQL语句,直接用生成的方法来调用Mapper接口,比以前一个一个的去写SQL语句方便多了…
|