【后端项目】苍穹外卖day01-开发环境搭建

苍穹外卖

最适合新手入门的SpringBoot+SSM企业级实战项目

SSM 是 Java 后端入门的核心技术栈,指 Spring + SpringMVC + MyBatis,是传统企业开发的主流组合

Spring Boot 是对 SSM 的简化和增强(自动配置、快速启动),是现在企业开发的主流框架

开发方式:前后端分离;用户端实现微信小程序;服务端由有状态升级为无状态

基础数据模块、点餐业务模块、统计报表模块

项目效果展示

软件开发整体介绍

软件开发流程

需求分析:需求规格说明书(word文档)、产品原型(静态网页)

设计:UI设计(人工交互页面)、数据库设计(表)、接口设计(登录功能等)

编码:项目代码、单元测试(测试自己写的代码)

测试:测试用例、测试报告

上线运维:软件环境安装、配置

角色分工

项目经理:对整个项目负责

产品经理:需求调研,产品原型

UI设计师:界面效果图

架构师:整体架构设计,技术选型

开发工程师:代码实现(我们的工作)

测试工程师:测试用例

运维工程师:环境搭建、项目上线

软件环境

开发环境:开发人员在开发阶段使用的环境,本地环境

测试环境:给测试人员使用的环境,测试服务器

生产环境:线上环境,正式提供对外服务的环境

苍穹外卖项目介绍

项目介绍

定位:专门为餐饮企业定制的一款软件产品(管理端:外卖商家使用;用户端:点餐用户使用)

功能架构:业务功能模块(管理端:员工管理、分类管理、菜品管理、套餐管理、订单管理、工作台、数据统计、来单提醒;用户端:微信登录、商品浏览、购物车、用户下单、微信支付、历史订单、地址管理、用户催单)

产品原型

产品原型:展示项目的业务功能(html文件)

管理端:

用户端:

技术选型

技术选型:技术框架和中间件

开发环境搭建

前后端分离

前端环境搭建

整体结构:

前端:管理端(Web),用户端(小程序)

后端:后端服务(Java)

前端工程基于nginx运行,发好的前端代码部署到 Nginx 服务器上,由 Nginx 来负责接收用户的请求、处理并返回这些前端资源。前端开发完成后,会通过打包工具生成静态资源包,这些文件本身不能直接被用户访问,需要一个 "中间人" 来处理网络请求 ------Nginx 就是这个核心的 "中间人"

前端工程打包之后的效果

注:Nginx目录必须放在没有中文的目录中才能正常运行

双击nginx.exe文件即可运行:

后端环境搭建

熟悉项目结构

后端工程基于maven进行项目构建,并且进行分模块开发,Maven 是 Java 后端领域最常用的项目构建和依赖管理工具,用 Maven 这个工具来管理后端项目的依赖、编译、打包等全流程,同时把一个大的后端项目拆分成多个功能独立的小模块

做项目时是在有一部分基础代码上,去开发业务工程

使用Git进行版本控制

git是通过仓库来管理代码的,分为本地仓库和远程仓库

IntelliJ IDEA 是一款广受欢迎的集成开发环境(IDE),专为Java编程语言设计,但也支持其他语言,它由JetBrains公司开发,Community Edition(社区版):作为一个开源项目,社区版是免费的,但功能相对有限,主要支持Java和Kotlin开发

gitignore文件:

**/target/

.idea

*.iml

*.class

*Test.java

**/test/

Git 哪些文件 / 文件夹不需要被追踪(即不上传到代码仓库)

  1. 创建Git本地仓库:VCS版本控制系统

创建Git仓库:sky-take-out

提交到本地仓库:

  1. 创建Git远程仓库:
  1. 创建一个远程的仓库
  1. 将本地文件推送到Git远程仓库
数据库环境搭建

通过数据库建表语句创建数据库表结构:

导入sky.sql文件:

前后端联调

后端的初始工程已经实现了登录功能,直接进行前后端联调测试即可

  1. 浏览器 → Controller(控制层):浏览器发起请求后,Controller 作为入口层,主要做三件事:
    • 接收并封装参数:获取前端传来的用户名、密码等登录数据。
    • 调用 Service 方法:把业务逻辑的处理交给 Service 层,自己不直接操作数据库。
    • 封装结果并响应:拿到 Service 返回的结果后,整理成前端能识别的格式(如 JSON)并返回。
  2. Controller → Service(业务层):Service 是核心业务逻辑层,负责处理具体业务规则:
    • 调用 Mapper 查询数据库:根据用户名去数据库查询用户信息。
    • 密码比对:将前端传来的密码和数据库中查询到的加密密码进行校验。
    • 返回结果:把校验结果(登录成功 / 失败)返回给 Controller。
  3. Service → Mapper(数据访问层):Mapper 是直接和数据库交互的层:
    • 执行 SQL 语句:select * from employee where username = ?
    • 把查询结果封装成 Java 对象,返回给 Service 层。
  4. Mapper → 数据库:数据库执行 SQL 查询,返回匹配的员工数据,再依次回传给 Mapper、Service、Controller,最后由 Controller 响应给浏览器。

maven中编译:

下载jdk:

启动:

SkyApplication:启动类

// Spring Boot 项目的启动入口类,运行这个类就能启动整个后端程序
package com .sky ;
// 包声明:给这个类归个文件夹,避免和其他类重名;com.sky 是项目的根包名,所有代码都放在这个包下

import lombok .extern .slf4j .Slf4j ;
// 导入日志工具,用来打印运行信息
import org .springframework .boot .SpringApplication ;
import org .springframework .boot .autoconfigure .SpringBootApplication ;
// Spring Boot 的核心工具,负责启动项目、自动配置
import org .springframework .transaction .annotation .EnableTransactionManagement ;
// 用来开启数据库事务

@SpringBootApplication
// Spring Boot 的总注解,三合一功能:自动扫描项目中的所有组件;自动配置 Spring 环境;排除不需要的配置.加了这个注解,项目才能正常启动
@EnableTransactionManagement
//开启注解方式的事务管理:操作数据库时,如果出错,数据会自动回滚,不会乱掉
@Slf4j
// 日志注解,加了之后可以直接用 log.info() 打印日志
public class SkyApplication {

public static void main(String[] args) {

SpringApplication.run(SkyApplication.class, args);

log.info("server started");

}
}
// main 方法:Java 程序的固定入口,运行代码必须从这里开始
// SpringApplication.run(...):真正启动 Spring Boot 服务的代码
// log.info("server started");:打印日志:服务已启动

启动前端:

双击nginx.exe,或在 nginx-1.20.2 文件夹的地址栏输入 cmd,回车打开命令提示符,输入

启动命令

start nginx

验证是否启动成功

tasklist | findstr nginx

打开浏览器,输入地址,访问前端页面

http://localhost:80

admin 123456

MySQL 数据库插入语句:

往员工表(employee)里添加一条管理员数据

INSERT INTO `employee` VALUES (1,'管理员','admin','123456','13812312312','1','110101199001010047',1,'2022-02-15 15:51:20','2022-02-17 09:16:20',10,1);

select * from sky_take_out.employee;

application-dev.yml文件:

sky:

datasource:

driver-class -name: com.mysql.cj.jdbc.Driver

host: localhost

// 数据库在本机

port: 3306

// MySQL固定端口

database: sky_take_out

username: root

password: 123456

// 数据库名字、用户名和密码

断点调试跟踪代码:debug(小虫子)

controller.admin文件中的EmployeeController.java文件

// 后端登录的控制器
// 作用:专门处理管理端员工的登录和退出:接收前端传来的账号密码;调用 Service 层校验登录;登录成功 → 生成 JWT 令牌(登录凭证);返回给前端:员工信息 + token;提供退出接口

/**

* 员工管理

*/
@RestController
// 声明这是接口控制器,返回 JSON 数据
@RequestMapping("/admin/employee")
// 接口前缀,所有接口都以这个开头
@Slf4j
// 可以用 log.info() 打印日志,方便排查问题
public class EmployeeController {

@Autowired

private EmployeeService employeeService;

// 员工业务逻辑(校验账号密码)

@Autowired

private JwtProperties jwtProperties;

// JWT配置(密钥、过期时间)

/**

* 登录

*
* @param employeeLoginDTO

* @return

*/

@PostMapping("/login")

public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {

log.info("员工登录:{}", employeeLoginDTO);

// 打印前端传过来的登录信息

Employee employee = employeeService.login(employeeLoginDTO);

// 调用Service,校验用户名密码,返回员工对象

//登录成功后,生成jwt令牌(前端以后靠这个证明已登录)

Map<String, Object> claims = new HashMap<>();

claims.put(JwtClaimsConstant.EMP_ID, employee.getId()); // 把员工ID放进token

String token = JwtUtil.createJWT(

jwtProperties.getAdminSecretKey(), // 密钥

jwtProperties.getAdminTtl(), // 过期时间

claims); // 存放的数据

// 封装要返回给前端的数据

EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()

.id(employee.getId())

.userName(employee.getUsername())

.name(employee.getName())

.token(token) // 把token返回给前端

.build();

// 统一返回格式:成功 + 数据

return Result.success(employeeLoginVO);

}

/**

* 退出

*
* @return

*/

// 退出只需要前端删除 token 就行,后端直接返回成功即可

@PostMapping("/logout")

public Result<String> logout() {

return Result.success();

}

}

单步调试:Fn+F8

接收前端传过来的登录信息:

调用Service,校验用户名密码,返回员工对象:

ctrl+鼠标:接口:规定登录规则

// Employee employee = employeeService.login(employeeLoginDTO);
// EmployeeService 接口:传入:前端给的账号密码(EmployeeLoginDTO);返回:查询到的员工对象(Employee);给我账号密码,我给你返回登录成功的员工信息

public interface EmployeeService {

/**

* 员工登录
* @param employeeLoginDTO

* @return

*/

Employee login(EmployeeLoginDTO employeeLoginDTO);

}

ctrl+alt+鼠标:实现类:真正实现登录

// Employee employee = employeeService.login(employeeLoginDTO);

@Service
public class EmployeeServiceImpl implements EmployeeService {

@Autowired

private EmployeeMapper employeeMapper;

/**

* 员工登录

*
* @param employeeLoginDTO

* @return

*/

public Employee login(EmployeeLoginDTO employeeLoginDTO) {

// 拿到前端传的账号、密码

String username = employeeLoginDTO.getUsername();

String password = employeeLoginDTO.getPassword();

// 根据用户名查询数据库中的数据

Employee employee = employeeMapper.getByUsername(username);

// 处理各种异常情况(用户名不存在、密码不对、账号被锁定)

if (employee == null ) {

//账号不存在

throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);

}

// 密码比对

// TODO 后期需要进行md5加密,然后再进行比对

if (!password.equals(employee.getPassword())) {

// 密码错误

throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);

}

if (employee.getStatus() == StatusConstant.DISABLE) {

// 账号被锁定

throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);

}

// 全部正确 → 返回员工信息

return employee;

}

}

// EmployeeMapper.java:根据用户名来查询员工信息
// 使用sql语句的方法

@Mapper
public interface EmployeeMapper {

/**

* 根据用户名查询员工
* @param username

* @return

*/

@Select("select * from employee where username = #{username}")

Employee getByUsername(String username);

}

// AccountNotFoundException继承BaseException(全局异常)

/**

* 账号不存在异常

*/
public class AccountNotFoundException extends BaseException {

public AccountNotFoundException() {

}

public AccountNotFoundException(String msg) {

super (msg);

}

}

// PasswordErrorException继承BaseException(全局异常)

/**

* 密码错误异常

*/
public class PasswordErrorException extends BaseException {

public PasswordErrorException() {

}

public PasswordErrorException(String msg) {

super (msg);

}

}

//sky-common的exception的BaseException(全局异常)

/**

* 业务异常

*/
public class BaseException extends RuntimeException {

public BaseException() {

}

public BaseException(String msg) {

super (msg);

}

}

// handler的GlobalExceptionHandler.java文件
// 全局异常处理器,捕获项目中抛出的业务异常,给前端页面返回异常结果

/**

* 全局异常处理器,处理项目中抛出的业务异常

*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

/**

* 捕获业务异常
* @param ex

* @return

*/

@ExceptionHandler

public Result exceptionHandler(BaseException ex){

log.error("异常信息:{}", ex.getMessage());

return Result.error(ex.getMessage());

}

}

// StatusConstant.java文件

/**

* 状态常量,启用或者禁用

*/
public class StatusConstant {

//启用

public static final Integer ENABLE = 1;

//禁用

public static final Integer DISABLE = 0;
}

登录成功后,生成jwt令牌(前端以后靠这个证明已登录)

//登录成功后,生成jwt令牌(前端以后靠这个证明已登录)

Map<String, Object> claims = new HashMap<>(); // 创建一个空盒子,用来存放要放进 JWT 里的数据

claims.put(JwtClaimsConstant.EMP_ID, employee.getId()); // 把员工ID放进token

String token = JwtUtil.createJWT(

jwtProperties.getAdminSecretKey(), // 密钥

jwtProperties.getAdminTtl(), // 过期时间

claims); // 存放的数据

// 封装要返回给前端的数据

// Builder:建造器,用来快速创建对象

EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()

.id(employee.getId())

.userName(employee.getUsername())

.name(employee.getName())

.token(token) // 把token返回给前端

.build();

// JWT Token 里的自定义字段(Claims)名称

public class JwtClaimsConstant {

public static final String EMP_ID = "empId";

public static final String USER_ID = "userId";

public static final String PHONE = "phone";

public static final String USERNAME = "username";

public static final String NAME = "name";

}

// sky-common的properties的JwtProperties
// JWT 配置类,专门用来从配置文件(application.yml文件)里读取 JWT 相关的配置信息,然后封装成一个 Java 对象

@Component
@ConfigurationProperties(prefix = "sky.jwt")
@Data
public class JwtProperties {

/**

* 管理端员工生成 jwt令牌相关配置

*/

private String adminSecretKey;

private long adminTtl;

private String adminTokenName;

/**

* 用户端微信用户生成 jwt令牌相关配置

*/

private String userSecretKey;

private long userTtl;

private String userTokenName;

}

// 配置文件:resources的application.yml文件

sky:

jwt:

设置jwt签名加密时使用的秘钥

admin-secret-key: itcast

设置jwt过期时间

admin-ttl: 7200000

设置前端传递过来的令牌名称

admin-token-name: token

// 给前端返回登录成功数据的模板类

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(description = "员工登录返回的数据格式")
public class EmployeeLoginVO implements Serializable {

@ApiModelProperty("主键值")

private Long id;

@ApiModelProperty("用户名")

private String userName;

@ApiModelProperty("姓名")

private String name;

@ApiModelProperty("jwt令牌")

private String token;

}

前端发送的请求,是如何请求到后端服务的?

Ctrl+Shift+i:打开控制面板

前端:

后端:

// EmployeeController.java文件

@RequestMapping("/admin/employee")

@PostMapping("/login")

// application.yml文件

server:

port: 8080

nginx.conf文件

用户访问前端页面时,Nginx 自动把 /user/ 开头的请求,转发给后端 Java 服务(127.0.0.1:8080)

upstream webservers{

server 127.0.0.1:8080 weight=90 ;

}

反向代理,处理用户端发送的请求

location /user/ {

proxy_pass http://webservers/user/;

}

完善登录功能

MD5加密方式:(不可逆)

修改数据库密码:

提交即可

修改java代码:

todo面板:View → Tool Windows → TODO 打开

//进行md5加密,然后再进行比对

password = DigestUtils.md5DigestAsHex(password.getBytes());
if (!password.equals(employee.getPassword())) {

//密码错误

throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
}

导入接口文档

开发方式基于前后端分离的开发方式,所以需要将接口定义后,为后面的业务开发做好准备

接口的设计其实需要长期讨论

前后端分离开发流程

操作步骤

管理端接口和用户端接口

Apifox

两个json文件,所以两个项目

Swagger

帮助后端生成接口文档,并且进行在线接口测试

介绍

使用方式

// sky-server的config
// 通过knife4j生成接口文档

@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;
}

// 设置静态资源映射

protected void addResourceHandlers(ResourceHandlerRegistry registry) {

registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");

registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");

}

访问地址:http://localhost:8080/doc.html

// 登入接口和登出接口

/**

* 员工管理

*/
@RestController
@RequestMapping("/admin/employee")
@Slf4j
public class EmployeeController {

@Autowired

private EmployeeService employeeService;

@Autowired

private JwtProperties jwtProperties;

/**

* 登录

*
* @param employeeLoginDTO

* @return

*/

@PostMapping("/login")

public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {

log.info("员工登录:{}", employeeLoginDTO);

Employee employee = employeeService.login(employeeLoginDTO);

//登录成功后,生成jwt令牌

Map<String, Object> claims = new HashMap<>();

claims.put(JwtClaimsConstant.EMP_ID, employee.getId());

String token = JwtUtil.createJWT(

jwtProperties.getAdminSecretKey(),

jwtProperties.getAdminTtl(),

claims);

EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()

.id(employee.getId())

.userName(employee.getUsername())

.name(employee.getName())

.token(token)

.build();

return Result.success(employeeLoginVO);

}

/**

* 退出

*
* @return

*/

@PostMapping("/logout")

public Result<String> logout() {

return Result.success();

}

}

测试:

常用注解

相关推荐
lzksword2 小时前
C++ Builder XE OpenDialog1打开多文件并显示xls与xlsx二种格式文件
java·前端·c++
青槿吖2 小时前
【保姆级教程】Spring事务控制通关指南:XML+注解双版本,避坑指南全奉上
xml·java·开发语言·数据库·sql·spring·mybatis
Yungoal2 小时前
B/S和C/S架构在服务端接收请求
c语言·开发语言·架构
niceffking2 小时前
C++内部类的ISO约定和语法细节
开发语言·c++
mygljx2 小时前
spring-ai 下载不了依赖spring-ai-openai-spring-boot-starter
java·人工智能·spring
wjs20242 小时前
C# 常量
开发语言
Ma_Hong_Kai2 小时前
CMFCRibbonBar
开发语言·visualstudio·mfc
jaysee-sjc2 小时前
【练习十二】Java实现年会红包雨小游戏
java·开发语言·算法·游戏·intellij-idea
LONGZETECH2 小时前
新能源汽车充电设备装配与调试仿真教学软件 技术解析与教学落地
开发语言·系统架构·汽车·汽车教学软件·智能网联汽车软件