Day-1
开发环境搭建
git
.gitinore设置忽略哪些文件的git
**/target/ .idea *.iml *.class *Test.java **/test/
1.创建本地仓库
变颜色+出现git图标
现无远程仓库,先commit
2.创建远程仓库
账号为手机号,密码为正常密码
3.本地代码提交远程
点击push
数据库环境搭建
1.打开mysql服务先
2.navicat新建查询→运行
前后端联调
1.双击compile 编译
保证项目编译通过
问题一: Maven导入依赖、编译速度慢
解决视频链接step1.在maven的setting.xml下添加阿里云镜像
step2.复制到IDEA与maven约定的默认地址
因为IDEA默认使用与maven约定的地址,在C:\Users\金海宇\.m2下找setting.xml,所以将修改好的setting.xml复制过去
2.运行server的application,实现登录
问题二:数据库连接问题
2023-12-26 16:04:41.414 ERROR 13996 --- [eate-1875304119] com.alibaba.druid.pool.DruidDataSource : create connection SQLException, url: jdbc:mysql://localhost:3306/sky_take_out?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true, errorCode 1045, state 28000
java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-connector-java-8.0.30.jar:8.0.30]
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.30.jar:8.0.30]
at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:828) ~[mysql-connector-java-8.0.30.jar:8.0.30]
at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:448) ~[mysql-connector-java-8.0.30.jar:8.0.30]
at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:241) ~[mysql-connector-java-8.0.30.jar:8.0.30]
at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:198) ~[mysql-connector-java-8.0.30.jar:8.0.30]
at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1651) ~[druid-1.2.1.jar:1.2.1]
at com.alibaba.druid.pool.DruidAbstractDataSource.createPhysicalConnection(DruidAbstractDataSource.java:1717) ~[druid-1.2.1.jar:1.2.1]
at com.alibaba.druid.pool.DruidDataSource$CreateConnectionThread.run(DruidDataSource.java:2785) ~[druid-1.2.1.jar:1.2.1]
修改数据库账号密码
3.断点调试跟踪代码
目的:看功能如何实现
1.debug运行
2.打断点
3.前端再次登录
点step over进行下一句代码
进入login方法中加断点,让程序停在此处,点击放行按钮到下一个断点或运行结束
debug后的代码能看到其具体的值
总结:
step over一句一句 F8
resume program直接到下一个断点或执行完 F9
完善登录功能
TODO用于标记尚未完成的功能
下次打开直接TODO锁定未完成的位置
navicat修改完记得点下面√保存
导入接口文档
YApi 导入接口文档
swagger接口测试
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
/**
* 通过knife4j生成接口文档
* @return
*/
@Bean
public Docket docket() {
ApiInfo apiInfo = new ApiInfoBuilder()
.title("苍穹外卖项目接口文档")
.version("2.0")
.description("苍穹外卖项目接口文档")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo)
.select()
.apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
.paths(PathSelectors.any())
.build();
return docket;
}
/**
* 设置静态资源映射
* @param registry
*/
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
若无静态资源映射,springmvc框架不认为/doc.html是静态页面,而以为是某个controller
运行代码
Day-2
新增员工
DTO接收前端数据
alt+回车——创建方法
右上绿色圆标可跳转到Impl
alt+回车 创建方法
瞄准镜图标——锁定路径
常量类——统一修改常量
step1:创建DTO封装前端传来的数据
sky-pojo/src/main/java/com/sky/dto/EmployeeDTO.java
@Data
public class EmployeeDTO implements Serializable {
private Long id;
private String username;
private String name;
private String phone;
private String sex;
private String idNumber;
}
step2: controller创建方法,调用service
sky-server/src/main/java/com/sky/controller/admin/EmployeeController.java
/**
* 新增员工
* @param employeeDTO
* @return
*/
@PostMapping
@ApiOperation("新增员工")
public Result save(@RequestBody EmployeeDTO employeeDTO){
log.info("新增员工:{}",employeeDTO);
employeeService.save(employeeDTO);
return Result.success();
}
step3:service创建方法,绿色圆I图标跳到Impl
sky-server/src/main/java/com/sky/service/EmployeeService.java
/**
* 新增员工
* @param employeeDTO
*/
void save(EmployeeDTO employeeDTO);
step4:Impl实现方法
sky-server/src/main/java/com/sky/service/impl/EmployeeServiceImpl.java
/**
* 新增员工
* @param employeeDTO
*/
@Override
public void save(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
若属性名不一致,一个一个设置
//employee.setName(employeeDTO.getName());
//对象属性拷贝
//属性名一致,一次性拷贝属性
BeanUtils.copyProperties(employeeDTO,employee);
//设置账号状态
employee.setStatus(StatusConstant.ENABLE);
//设置账号密码
employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
//设置当前记录创建时间和修改时间
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
//设置当前记录创建人和修改人id
//TODO 后期改为当前登录用户id
employee.setCreateUser(10L);
employee.setUpdateUser(10L);
//封装完毕,调用持久层插入
employeeMapper.insert(employee);
}
step5:mapper实现数据库操作
sky-server/src/main/resources/application.yml
mybatis:
#mapper配置文件
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.sky.entity
configuration:
#开启驼峰命名
map-underscore-to-camel-case: true
sky-server/src/main/java/com/sky/mapper/EmployeeMapper.java
简单数据库操作直接注解即可
/**
* 插入员工数据
* @param employee
*/
@Insert("insert into employee (name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user, status) " +
" values " +
"(#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})")
void insert(Employee employee);
配置SQL提示
功能测试
返回401,因为没有通过jwt拦截器 ,令牌校验不通过,所以要在接口文档统一添加一个jwt令牌
代码完善
控制台无异常,因为数据库抛出的 异常被异常处理方法捕获。
/**
* 处理SQL异常
* @param ex
* @return
*/
@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
//Duplicate entry 'zhangsan' for key 'idx_username'
String message = ex.getMessage();
if (message.contains("Duplicate entry")){
//根据空格分隔成数组
String[] split = message.split(" ");
String username = split[2];
String msg = username + MessageConstant.ALREADY_EXISTS;
return Result.error(msg);
}else {
return Result.error(MessageConstant.UNKNOWN_ERROR);
}
}
每一次请求为一个线程
拦截器处存进去
service处取出来
员工分页查询
@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的),所以只能发送POST请求。
GET方式无请求体,所以使用@RequestBody接收数据时,前端不能使用GET方式提交数据,而是用POST方式进行提交。
在后端的同一个接收方法里,@RequestBody与@RequestParam()可以同时使用,@RequestBody最多只能有一个,而@RequestParam()可以有多个。
注:一个请求,只有一个RequestBody;一个请求,可以有多个RequestParam。
step1: DTO,Result,PageResult
sky-pojo/src/main/java/com/sky/dto/EmployeePageQueryDTO.java
@Data
public class EmployeePageQueryDTO implements Serializable {
//员工姓名
private String name;
//页码
private int page;
//每页显示记录数
private int pageSize;
}
sky-common/src/main/java/com/sky/result/PageResult.java
/**
* 封装分页查询结果
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {
private long total; //总记录数
private List records; //当前页数据集合
}
sky-common/src/main/java/com/sky/result/Result.java
/**
* 后端统一返回结果
* @param <T>
*/
@Data
public class Result<T> implements Serializable {
private Integer code; //编码:1成功,0和其它数字为失败
private String msg; //错误信息
private T data; //数据
public static <T> Result<T> success() {
Result<T> result = new Result<T>();
result.code = 1;
return result;
}
public static <T> Result<T> success(T object) {
Result<T> result = new Result<T>();
result.data = object;
result.code = 1;
return result;
}
public static <T> Result<T> error(String msg) {
Result result = new Result();
result.msg = msg;
result.code = 0;
return result;
}
}
step2: controller
因为前端传的数据格式是query不是json,所以无需@RequestBody,spring mvc框架可正常将数据封装好
sky-server/src/main/java/com/sky/controller/admin/EmployeeController.java
/**
* 员工分页查询
* @param employeePageQueryDTO
* @return
*/
@GetMapping("/page")
@ApiOperation("员工分页查询")
public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){
//为了方便调试
log.info("员工分页查询:{}",employeePageQueryDTO);
//pageResult就是data,包含total,records
PageResult pageResult = employeeService.pageQuery(employeePageQueryDTO);
//返回的是全部code,msg,data
return Result.success(pageResult);
}
step3: service
sky-server/src/main/java/com/sky/service/EmployeeService.java
/**
* 分页查询
* @param employeePageQueryDTO
* @return
*/
PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
step4: Impl
分页查询插件
sky-server/pom.xml
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper}</version>
</dependency>
sky-server/src/main/java/com/sky/service/impl/EmployeeServiceImpl.java
/**
* 分页查询
* @param employeePageQueryDTO
* @return
*/
@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
// select * from employee limit 0,10 前端传入DTO,根据之动态计算拼接到SQL语句中,用插件方便操作
//开始分页查询
PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());
Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);
long total = page.getTotal();
List<Employee> records = page.getResult();
return new PageResult(total,records);
}
step5: mapper
sky-server/src/main/java/com/sky/mapper/EmployeeMapper.java
/**
* 分页查询
* @param employeePageQueryDTO
* @return
*/
Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
step6: 映射文件
小鸟下载mybatisx插件
动态SQL用注解不方便,所以映射文件
sky-server/src/main/resources/mapper/EmployeeMapper.xml
<?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="com.sky.mapper.EmployeeMapper">
<select id="pageQuery" resultType="com.sky.entity.Employee">
select * from employee
<where>
<if test="name != null and name != '' ">
and name like concat('%',#{name},'%')
</if>
</where>
order by create_time desc
</select>
</mapper>
映射文件被项目扫描到
sky-server/src/main/resources/application.yml
mybatis:
#mapper配置文件
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.sky.entity
configuration:
#开启驼峰命名
map-underscore-to-camel-case: true
功能测试
返回401,因为token有有效期7200000毫秒,即去三0秒,2h
sky:
jwt:
# 设置jwt签名加密时使用的秘钥
admin-secret-key: itcast
# 设置jwt过期时间
admin-ttl: 7200000
# 设置前端传递过来的令牌名称
admin-token-name: token
所以swagger重新登陆获取,注意要叉掉标签页,重新进入
最后操作时间格式有问题
代码完善
法一,单个处理
法二,统一处理(推荐)
固定代码
sky-common/src/main/java/com/sky/json/JacksonObjectMapper.java
package com.sky.json;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
//public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
sky-server/src/main/java/com/sky/config/WebMvcConfiguration.java
/**
* 扩展是Spring MVC框架的消息转换器
* 程序启动时被调用
* @param converters
*/
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转化器...");
//创建一个消息转换器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//需要为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列化为json数据
converter.setObjectMapper(new JacksonObjectMapper());
//将自己的消息转换器加入容器中, 0表示优先级为第一个,因为converters里自带许多消息转换器,默认为最后一个
converters.add(0,converter);
}
启用禁用员工账号
非查询不用泛型
step1: controller
/**
* 启用禁用员工账号
* @param status
* @param id
*/
@PostMapping("/status/{status}")
@ApiOperation("启用禁用员工账号")
public Result startOrStop(@PathVariable Integer status,Long id){
log.info("启用禁用员工账号:{},{}",status,id);
employeeService.startOrStop(status,id);
return Result.success();
}
step2: service
/**
* 启用禁用员工账号
* @param status
* @param id
*/
void startOrStop(Integer status, Long id);
step3: Impl
动态update,可复用
/**
* 启用禁用员工账号
* @param status
* @param id
*/
@Override
public void startOrStop(Integer status, Long id) {
//本质:根据id修改status
//update employee set status = ? where id = ?
// Employee employee = new Employee();
// employee.setStatus(status);
// employee.setId(id);
//Employee有@Builder注解,可用下面方式,作用同上,根据喜好选择
Employee employee = Employee.builder()
.status(status)
.id(id)
.build();
employeeMapper.update(employee);
}
step4: mapper
/**
* 根据主键动态修改属性
* @param employee
*/
void update(Employee employee);
step5: 映射文件
注意数据库并未驼峰命名且不区分大小写
<update id="update" parameterType="Employee">
update employee
<set>
<if test="name != null">name = #{name},</if>
<if test="username != null">username = #{username},</if>
<if test="password != null">password = #{password},</if>
<if test="phone != null">phone = #{phone},</if>
<if test="sex != null">sex = #{sex},</if>
<if test="idNumber != null">id_Number = #{idNumber},</if>
<if test="status != null">status = #{status},</if>
<if test="updateTime != null">update_Time = #{updateTime},</if>
<if test="updateUser != null">update_User = #{updateUser},</if>
</set>
where id = #{id}
</update>
功能测试
commit push
编辑员工
根据id查询员工信息
step1: controller
/**
* 根据id查询员工信息
* @param id
* @return
*/
@GetMapping("/{id}")
@ApiOperation("根据id查询员工信息")
public Result<Employee> getById(@PathVariable Long id){
Employee employee = employeeService.getById(id);
return Result.success(employee);
}
step2: service
/**
* 根据id查询员工信息
* @param id
* @return
*/
Employee getById(Long id);
step3: Impl
/**
* 根据id查询员工信息
* @param id
* @return
*/
@Override
public Employee getById(Long id) {
Employee employee = employeeMapper.getById(id);
employee.setPassword("****");
return employee;
}
step4: mapper
/**
* 根据id查询员工信息
* @param id
* @return
*/
@Select("select * from employee where id = #{id}")
Employee getById(Long id);
功能测试
编辑员工信息
C
/**
* 编辑员工信息
* @param employeeDTO
* @return
*/
@PutMapping
@ApiOperation("编辑员工信息")
// 只有查询性操作才需要指定泛型,前端传的是json格式,所以加@RequestBody
public Result update(@RequestBody EmployeeDTO employeeDTO){
employeeService.update(employeeDTO);
return Result.success();
}
S
/**
* 编辑员工信息
* @param employeeDTO
* @return
*/
void update(EmployeeDTO employeeDTO);
I
/**
* 编辑员工信息
* @param employeeDTO
* @return
*/
@Override
public void update(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
若属性名不一致,一个一个设置
//employee.setName(employeeDTO.getName());
//对象属性拷贝
//属性名一致,一次性拷贝属性
BeanUtils.copyProperties(employeeDTO,employee);
employee.setUpdateTime(LocalDateTime.now());
employee.setUpdateUser(BaseContext.getCurrentId());
employeeMapper.update(employee);
}
M
/**
* 根据主键动态修改属性
* @param employee
*/
void update(Employee employee);
Y
<update id="update" parameterType="Employee">
update employee
<set>
<if test="name != null">name = #{name},</if>
<if test="username != null">username = #{username},</if>
<if test="password != null">password = #{password},</if>
<if test="phone != null">phone = #{phone},</if>
<if test="sex != null">sex = #{sex},</if>
<if test="idNumber != null">idNumber = #{idNumber},</if>
<if test="status != null">status = #{status},</if>
<if test="updateTime != null">updateTime = #{updateTime},</if>
<if test="updateUser != null">updateUser = #{updateUser},</if>
</set>
where id = #{id}
</update>
报错:空指针问题,此处是因为查询没有的id
2024-01-03 14:23:33.900 ERROR 8560 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
java.lang.NullPointerException: null
报错: 执行的SQL语句中引用了数据库表中不存在的列
2024-01-03 14:34:50.719 ERROR 8560 --- [nio-8080-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.jdbc.BadSqlGrammarException:
### Error updating database. Cause: java.sql.SQLSyntaxErrorException: Unknown column 'idNumber' in 'field list'
原因:数据库中并非驼峰命名,数据库不区分大小写
<update id="update" parameterType="Employee">
update employee
<set>
<if test="name != null">name = #{name},</if>
<if test="username != null">username = #{username},</if>
<if test="password != null">password = #{password},</if>
<if test="phone != null">phone = #{phone},</if>
<if test="sex != null">sex = #{sex},</if>
<if test="idNumber != null">id_Number = #{idNumber},</if>
<if test="status != null">status = #{status},</if>
<if test="updateTime != null">update_Time = #{updateTime},</if>
<if test="updateUser != null">update_User = #{updateUser},</if>
</set>
where id = #{id}
</update>
最后2测试+CM
导入分类管理功能代码
代码导入
从后往前导(ctrlCV),以免报错MY SI C
导入代码可能不会自动编译,手动编译一下
最后2测试+CM