1. 系统简介
本购物系统分为管理端和用户端两部分,旨在实现高效的商品管理与用户购物体验。管理端为管理员提供核心操作功能,包括用户管理、商品管理和订单管理。管理员可通过系统对用户信息进行查询、增删改查商品及其库存,并支持条件查询订单。
用户端面向公众和会员,功能丰富。公共模块支持商品分类浏览、商品详情和评价查看以及问题咨询。会员模块进一步提供登录注册、购物车管理、订单查询及评价功能。用户可通过平台轻松完成商品的购买流程,包括加入购物车、一键下单等操作。
整体系统设计注重用户体验,功能划分清晰,操作便捷,为商家和用户提供了高效、友好的购物服务。
项目仓库地址:https://gitee.com/enhead/shop
2. 系统功能模块图
3. 表设计
表1 管理员信息表(admins)
列名 | 数据类型 | 长度 | 是否为空 | 描述 |
---|---|---|---|---|
id | BIGINT | – | 否 | 管理员ID(主键) |
account | VARCHAR | 255 | 否 | 管理员账号(唯一) |
name | VARCHAR | 64 | 否 | 管理员姓名 |
pwd | VARCHAR | 255 | 否 | 管理员密码 |
表2 用户信息表(users)
列名 | 数据类型 | 长度 | 是否为空 | 描述 |
---|---|---|---|---|
id | BIGINT | – | 否 | 用户ID(主键) |
VARCHAR | 64 | 否 | 用户邮箱(唯一) | |
pwd | VARCHAR | 255 | 否 | 用户密码 |
nickname | VARCHAR | 64 | 否 | 用户昵称(唯一) |
sex | INT | – | 否 | 性别(0-保密,1-男性,2-女性) |
recipient | VARCHAR | 64 | 是 | 收件人姓名 |
address | VARCHAR | 500 | 是 | 收货地址 |
phone | VARCHAR | 64 | 是 | 联系电话 |
headimg | VARCHAR | 500 | 否 | 用户头像 |
updatetime | DATETIME | – | 否 | 更新时间 |
createtime | DATETIME | – | 否 | 创建时间 |
表3 商品类别信息表(types)
列名 | 数据类型 | 长度 | 是否为空 | 描述 |
---|---|---|---|---|
id | BIGINT | – | 否 | 类别ID(主键) |
name | VARCHAR | 255 | 否 | 类别名称 |
表4 商品信息表(goods)
列名 | 数据类型 | 长度 | 是否为空 | 描述 |
---|---|---|---|---|
id | BIGINT | – | 否 | 商品ID(主键) |
name | VARCHAR | 500 | 否 | 商品名称 |
typeId | BIGINT | – | 否 | 商品类别ID |
img | VARCHAR | 500 | 是 | 商品图片URL |
desc | TEXT | – | 是 | 商品描述 |
updatetime | DATETIME | – | 否 | 更新时间 |
createtime | DATETIME | – | 否 | 创建时间 |
表5 商品规格信息表(goodsDetails)
列名 | 数据类型 | 长度 | 是否为空 | 描述 |
---|---|---|---|---|
id | BIGINT | – | 否 | 商品详情ID(主键) |
goodsId | BIGINT | – | 否 | 商品ID |
specName | VARCHAR | 500 | 否 | 规格名称 |
stockNum | INT | – | 否 | 库存数量 |
unitPrice | FLOAT | – | 否 | 单价 |
updatetime | DATETIME | – | 否 | 更新时间 |
createtime | DATETIME | – | 否 | 创建时间 |
表6 商品评价信息表(comments)
列名 | 数据类型 | 长度 | 是否为空 | 描述 |
---|---|---|---|---|
id | BIGINT | – | 否 | 评价ID(主键) |
userId | BIGINT | – | 否 | 评价用户ID |
goodsId | BIGINT | – | 否 | 商品ID |
goodsDetailId | BIGINT | – | 否 | 商品规格详情ID |
orderId | BIGINT | – | 否 | 订单ID |
content | VARCHAR | 500 | 是 | 评价内容 |
score | INT | – | 否 | 评分(1-5分) |
createtime | DATETIME | – | 否 | 创建时间 |
表7 留言信息表(messages)
列名 | 数据类型 | 长度 | 是否为空 | 描述 |
---|---|---|---|---|
id | BIGINT | – | 否 | 留言ID(主键) |
userId | BIGINT | – | 否 | 用户ID |
goodsId | BIGINT | – | 否 | 商品ID |
content | VARCHAR | 500 | 否 | 留言内容 |
state | INT | – | 否 | 留言状态(0-未回复,1-已回复) |
createtime | DATETIME | – | 否 | 创建时间 |
表8 订单信息表(orders)
列名 | 数据类型 | 长度 | 是否为空 | 描述 |
---|---|---|---|---|
id | BIGINT | – | 否 | 订单ID(主键) |
userId | BIGINT | – | 否 | 用户ID |
goodsDetailId | BIGINT | – | 否 | 商品规格详情ID |
goodsNum | INT | – | 否 | 商品数量 |
amount | FLOAT | – | 否 | 订单金额 |
state | INT | – | 否 | 订单状态 |
updatetime | DATETIME | – | 否 | 更新时间 |
createtime | DATETIME | – | 否 | 创建时间 |
表9 留言回复信息表(replies)
列名 | 数据类型 | 长度 | 是否为空 | 描述 |
---|---|---|---|---|
id | BIGINT | – | 否 | 回复ID(主键) |
messageId | BIGINT | – | 否 | 留言ID |
content | VARCHAR | 500 | 否 | 回复内容 |
createtime | DATETIME | – | 否 | 创建时间 |
实体关系图:(EDR图)
4. 系统实现
系统实现概述
本商城系统采用前后端分离架构,前端使用了 Vue.js 进行开发,后端基于 Servlet 技术实现,并通过反射机制简化实现了 SpringMVC 的基础注解功能。整个系统遵循 RESTful 规范,前后端通过 HTTP 协议进行交互,确保系统的简洁性与可扩展性。
在前端部分,采用 Vue.js 实现响应式的用户界面,配合 axios 实现与后端的 HTTP 请求交互。系统遵循 RESTful API 设计原则,前端通过 GET、POST 等 HTTP 请求方法与后端交互,操作商品、用户和订单等资源。数据格式主要采用 JSON,以保证数据传输的高效性与兼容性。
后端部分,通过传统的 Servlet 技术处理 HTTP 请求,并通过反射机制实现了简化版的 SpringMVC 控制层注解功能。通过自定义注解,如 @RestController、@RequestMapping和全局异常处理器 等,模拟了 SpringMVC 的请求映射和响应处理机制,使得系统能够更灵活地处理用户请求并返回相应的结果。同时,IOC 容器的实现通过反射扫描组件类并进行依赖注入,进一步简化了代码结构。
在团队合作方面,我们采用了 Git 进行版本控制,并通过 Gitee 进行代码托管和协作开发。API 文档的编写和管理使用了 ApiFox,确保了前后端开发的同步与接口的一致性。此外,团队成员之间通过不断的代码审查和讨论,确保了系统的高效开发和稳定性。
通过这些技术实现,本商城系统具备了高效、可扩展、易维护的特点,能够为用户提供流畅的购物体验。
4.1 技术选型
4.1.1 后端部分
本商城系统的后端基于Java开发,采用传统的 Servlet 和 JSP 技术进行搭建,注重简洁性和高效性,主要技术选型如下:
-
开发工具与环境
-
Maven:作为项目的构建和依赖管理工具,用于管理依赖库、简化项目的编译与打包过程。
-
Java 11:提供现代化语言特性,如模块化系统和改进的垃圾回收机制,提升开发效率和性能。
-
-
Web 技术
-
Jakarta Servlet API:处理 HTTP 请求和响应,实现核心的服务端逻辑。
-
Jakarta JSP API:用于动态生成 HTML 页面,支持简单的数据展示和用户交互。
-
Commons BeanUtils:提供便捷的 JavaBean 操作能力,提高代码的可读性与可维护性。
-
-
数据库与连接池
-
MySQL Connector:支持与 MySQL 数据库进行高效交互,提供稳定的数据库访问功能。
-
Druid:阿里巴巴开源的数据库连接池,具备高性能、稳定性和强大的监控功能。
-
Commons DBUtils:封装 JDBC 操作,简化数据库查询与事务处理。
-
-
数据处理与安全
-
FastJSON:高性能的 JSON 序列化与反序列化工具,用于后端与前端的数据传输。
-
JJWT:用于生成和验证 JWT(JSON Web Token),支持安全的身份认证与授权。
-
JBCrypt:基于 BCrypt 算法的密码加密工具,用于存储用户密码,增强系统安全性。
-
-
日志与调试
-
Log4j:高效、灵活的日志框架,用于记录系统运行状态,支持多种日志级别和输出方式。
-
Reflections:用于反射操作,便于动态加载类或处理运行时元数据。
-
4.1.2 前端部分
前端部分是有原型项目的,在此项目的项目做出一点小改动,项目地址:https://gitee.com/enhead/shop-prototype
本系统的前端部分采用了基于 Vue.js 的技术栈进行开发,具体选型如下:
-
核心框架
使用 Vue.js 作为核心框架,提供了响应式的数据绑定与组件化开发模式,能够快速构建高性能的单页应用程序(SPA)。
-
路由管理
采用 vue-router 进行前端路由管理,支持多页面间的路由跳转与参数传递,实现了视图的动态切换。
-
状态管理
借助 Vuex 实现全局状态管理,用于管理购物车、用户信息等共享数据,保证了组件间数据的统一性和可维护性。
-
HTTP请求
使用 axios 作为 HTTP 客户端,用于与后端 API 进行数据交互,支持请求拦截与响应处理,简化了异步请求的管理。 -
模块打包
使用 Webpack 作为打包工具,通过 vue-loader 和 babel 编译工具支持 Vue 单文件组件的开发及 ES6 语法的转译,提升了开发效率和代码兼容性。 -
样式处理
前端样式处理集成了 less 和 less-loader,支持灵活的样式预处理,提高了样式的可复用性和开发效率。 -
辅助工具
- html-webpack-plugin:生成自动引入依赖的 HTML 文件。
- copy-webpack-plugin:用于静态资源的拷贝和处理。
- friendly-errors-webpack-plugin:优化开发过程中的错误提示,提升开发体验。
-
浏览器兼容性
配置了 autoprefixer 和 browserslist,自动添加浏览器前缀,确保兼容大多数主流浏览器。
通过上述技术选型,系统的前端具备了高性能、模块化、易维护和高兼容性的特点,能够满足商城系统的复杂业务需求。
4.1.3 团队开发部分
本商城系统的开发团队使用了现代化的协作工具和版本控制系统,以确保开发过程高效、协同。主要的团队合作开发工具选型如下:
- 版本控制与代码托管
- Git:作为分布式版本控制系统,用于管理代码的版本、分支和合并,确保团队成员能够高效协作,避免代码冲突。
- Gitee:作为代码托管平台,提供 Git 仓库管理、协作开发和版本控制服务。Gitee 支持快速的代码提交与代码审查流程,提升团队协作效率。
- 接口文档与测试
- ApiFox:用于接口文档的编写与管理,支持团队成员共同编写、更新 API 文档,确保接口的一致性与可用性。ApiFox 还具备接口调试功能,帮助开发人员快速测试接口,保证后端与前端的接口对接准确无误。
4.2 系统框架实现
4.2.1 基于servelet、序列化和放射机制实现SpringMVC基础注解
4.2.1.1 功能概述
这段代码的核心可以分为两个部分:IOC容器和AOP请求处理器。
- IOC容器(依赖注入容器):
- 负责管理应用中的所有组件和服务实例。在本代码中,IOC容器通过
ApplicationContext
类提供,负责存储和获取控制器实例及其依赖对象。 - 通过注解扫描 (
@RestController
和@RestControllerAdvice
),IOC 容器自动识别并实例化控制器和全局异常处理类,并进行管理。 - 容器提供依赖注入功能,确保控制器和异常处理器能通过反射机制正确地被调用。
- 负责管理应用中的所有组件和服务实例。在本代码中,IOC容器通过
- AOP请求处理器:
- 通过注解驱动的方式(如
@GetMapping
,@PostMapping
等),AopRequestHandler
负责将 HTTP 请求映射到相应的控制器方法。 - 处理请求时,首先解析请求路径和请求方法,然后通过反射机制调用对应的控制器方法,并将请求参数(通过
@RequestParam
和@RequestBody
注解)传递给方法。 - 对于异常,AOP 请求处理器会根据配置的全局异常处理器(通过
@ExceptionHandler
注解)捕获并处理,返回 JSON 格式的错误信息。 - 通过
response
对象统一设置响应格式(如 JSON 格式和跨域支持)。
- 通过注解驱动的方式(如
简而言之,IOC容器负责管理应用中的组件实例,而AOP请求处理器通过注解配置和反射机制处理请求、参数解析和异常捕获等逻辑。
4.1.1.2 代码实现
package com.wawu.common.httpHandler;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
//TODO 待优化:请求路径处理冗余了
// 这里我规定/api,还会进行中心代理,返回json
@WebServlet("/api/*")
public class AopServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 通过AopRequestHandler处理请求
AopRequestHandler.handleRequest(request, response);
}
}
package com.wawu.common.httpHandler;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.wawu.common.IOC.ApplicationContext;
import com.wawu.common.annotation.controller.globalExceptionHandle.ExceptionHandler;
import com.wawu.common.annotation.controller.mapping.*;
import com.wawu.common.annotation.controller.parameter.RequestBody;
import com.wawu.common.annotation.controller.parameter.RequestParam;
import com.wawu.common.enumeration.RequestMethodEnum;
import com.wawu.common.property.TomcatProperty;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.beanutils.ConvertUtils;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 使用切面编程处理原生的Servlet
* 功能:
* 1. 路由注册以及控制层实例的映射
* 2. 分配实际的请求相应,并以json格式放回
*
* 包含注解(控制层相关注解的简化):
* 1. @RestController,@RequestMapping,@请求方式Mapping
* 2. 请求方法接受方法相关注解:@RequestBody,@RequestParam
*/
public class AopRequestHandler {
private static final Map<String, Method> routes = new HashMap<>();//请求的路由映射
private static final Map<String, Object> restControllers = new HashMap<>(); // 路由对应控制器实例(来自IOC容器中的bean对象)
//TODO:待优化: 1.如果需要简化代码:①请求路径以及方法可以放在@RequestMapping,但是这里还需要完成注解(类似`@AliasFor(annotation = Component.class)`用来处理别名);②简单的办法可以定义一个集合来枚举
// 2.支持多路径、多请求方式,这里默认只有一个路径
// 3.当@请求方式Mapping为空时,特判
/**
* 初始化路由
* - 注册路由
* - 注册请求控制对应的实例
* @param context:IOC容器
*/
public static void initializeRoutes(ApplicationContext context) {
//获取所有被@RestController修饰的类及其方法
for (Class<?> restcontrollerClass : context.getRestControllerClazzes()) {
Object restController = context.getBean(restcontrollerClass);
String basePath = "";
//类上的是父路径
if (restcontrollerClass.isAnnotationPresent(RequestMapping.class)) {
basePath = restcontrollerClass.getAnnotation(RequestMapping.class).value()[0];
}
//类方法上还有子路径
//合成完成的路由同时进行注册
for (Method method : restcontrollerClass.getDeclaredMethods()) {
String methodPath = ""; // 方法的路径,默认空
if (method.isAnnotationPresent(GetMapping.class)) {
methodPath = method.getAnnotation(GetMapping.class).value()[0];
registerRoute(basePath, methodPath, RequestMethodEnum.GET, method, restController);
} else if (method.isAnnotationPresent(PostMapping.class)) {
methodPath = method.getAnnotation(PostMapping.class).value()[0];
registerRoute(basePath, methodPath, RequestMethodEnum.POST, method, restController);
} else if (method.isAnnotationPresent(PutMapping.class)) {
methodPath = method.getAnnotation(PutMapping.class).value()[0];
registerRoute(basePath, methodPath, RequestMethodEnum.PUT, method, restController);
} else if (method.isAnnotationPresent(DeleteMapping.class)) {
methodPath = method.getAnnotation(DeleteMapping.class).value()[0];
registerRoute(basePath, methodPath, RequestMethodEnum.DELETE, method, restController);
}
}
}
System.out.println("基础请求路由:"+routes);
System.out.println("请求控制层:"+restControllers);
System.out.println("注意:默认是去掉Tomcat对应的上下文"+ TomcatProperty.CONTEXT_PATH);
}
/**
* 注册请求路径和方法映射
* @param basePath:类上的父路径
* @param methodPath:子类上的子路径
* @param methodEnum:请求方式
* @param method:放射中的方法
* @param controller:后续将要进行调用的实体类
*/
private static void registerRoute(String basePath, String methodPath, RequestMethodEnum methodEnum, Method method, Object controller) {
String fullPath = (basePath + methodPath).replaceAll("/+", "/");
routes.put(methodEnum.name() + fullPath, method);
restControllers.put(methodEnum.name() + fullPath, controller); // 记录控制器实例
}
/////从这里开始写注册异常处理部分/////
//大部分其实就是参照上面
private static final Map<Class<? extends Throwable>, Method> exceptionHandleMethod = new HashMap<>();//不同异常对应的请求方法
private static final Map<Class<? extends Throwable>, Object> restControllerAdvices = new HashMap<>(); //全局异常处理类(来自IOC容器中的bean对象)
//注意了下面也不管循环注解
/**
* 初始化全局异常处理方法
* - 注册异常处理方法:如果为空,则获取方法参数类型(TODO:注意:这里规定异常处理方法参数)
* - 注册异常处理对应的类
* @param context:IOC容器
*/
public static void initializeExceptionHandlers(ApplicationContext context) {
for (Class<?> RestControllerAdviceClass : context.getRestControllerAdviceClazzes()) {
Object restControllerAdvice = context.getBean(RestControllerAdviceClass);
// 遍历方法,查找@ExceptionHandler标注的方法
for (Method method : RestControllerAdviceClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(ExceptionHandler.class)) {
ExceptionHandler exceptionHandler = method.getAnnotation(ExceptionHandler.class);
// 获取注解中指定的异常类型,如果为空,则获取方法参数类型
Class<? extends Throwable>[] exceptionTypes = exceptionHandler.value();
if (exceptionTypes.length == 0) {
// 如果没有指定异常类型,则取方法的第一个参数类型作为异常类型
Parameter[] parameters = method.getParameters();
if (parameters.length > 0) {
Class<?> parameterType = parameters[0].getType();
if (Throwable.class.isAssignableFrom(parameterType)) {
exceptionTypes = new Class[]{(Class<? extends Throwable>) parameterType};
}
}
}
// 注册异常类型和方法的映射
for (Class<? extends Throwable> exceptionType : exceptionTypes) {
exceptionHandleMethod.put(exceptionType, method);
restControllerAdvices.put(exceptionType, restControllerAdvice); // 记录异常处理类实例
}
}
}
}
System.out.println("注册的异常处理方法:" + exceptionHandleMethod);
System.out.println("注册的异常处理控制器:" + restControllerAdvices);
}
/////注册异常处理部分结束/////
/**
* 处理请求(Servlet那边的)
* @param request
* @param response
* @throws IOException
*/
public static void handleRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
String httpMethod = request.getMethod();//请求方式
String relativePath = request.getRequestURI().substring(TomcatProperty.CONTEXT_PATH.length());//处理成类对应的请求路径
//这里是存在上下文环境的,就是tomcat中会进行单独配置,所以最好就是把这个放在配置文件中
//获取对应的处理类的路径
// 处理上下文路径为空的情况
//这时候访问:http://localhost:8083/api,上下文环境变为了"/api"
//如果在tomcat中配置了上下文是正常的
String routeKey = httpMethod + relativePath;
Method handlerMethod = routes.get(routeKey);
Object restController = restControllers.get(routeKey);
System.out.println("Requested Method and Path: " + httpMethod + " " + relativePath);
System.out.println("处理方法:" + handlerMethod+"|"+"控制层对应的实例:"+restController);
//(这里很重要)响应设置(在这里设置的话更好点,不用在多出设置)
//1.json响应设置
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
//2.跨域问题设置:
// 设置允许跨域的域名,*表示允许所有域名访问
response.setHeader("Access-Control-Allow-Origin", "*");
// 设置允许跨域的方法
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
// 设置允许跨域的请求头
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
// 是否允许携带凭证 (如 cookies)
response.setHeader("Access-Control-Allow-Credentials", "true");
if ("OPTIONS".equalsIgnoreCase(httpMethod)) {
response.setStatus(HttpServletResponse.SC_OK);
return;
}
//都不空才能处理请求
if (handlerMethod != null && restController != null) {
try {
handlerMethod.setAccessible(true);//破坏访问属性
Object[] args = resolveMethodParameters(handlerMethod, request);//处理请求获取控制层请求对应方法的参数
System.out.println("转换后的参数:"+ JSON.toJSONString(args));
Object result = handlerMethod.invoke(restController, args);//执行方法
// JSON 响应处理
String jsonResponse = JSONObject.toJSONString(result);
response.getWriter().write(jsonResponse);
} catch (Exception e) {
System.out.println("成功捕获异常,具体信息:");
e.printStackTrace();
// response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error processing request");
handleException(response,e);
}
} else {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Route not found");
}
}
/**
* 辅助方法:解析方请求的各种参数(主要为的是解析请求体参数和请求体内容)
* 场景:处理请求获取控制层请求对应方法的参数
* 规则:
* 1. **`@RequestBody`**:用于接受 JSON 请求体,将其转换为方法参数对象。
* 2. **`@RequestParam`**:用于从请求参数(URL 查询参数或表单参数)中获取特定的值并注入到方法参数中。
* 3. **默认参数映射**:若没有指定注解,则按参数名匹配请求参数,类似 `@RequestParam` 的功能。
* @param method:请求对应的处理方法
* @param request:请求
* @return
* @throws IOException
*/
private static Object[] resolveMethodParameters(Method method, HttpServletRequest request) throws IOException {
Parameter[] parameters = method.getParameters();
Object[] args = new Object[parameters.length];
String requestBody = null;
if ("POST".equalsIgnoreCase(request.getMethod()) || "PUT".equalsIgnoreCase(request.getMethod())) {
requestBody = request.getReader().lines().reduce("", String::concat);
}
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
if (parameter.isAnnotationPresent(RequestBody.class)) {
//TODO 探究:类型的转换这么整?
//DONE 数组类型是否可行,数组也能直接转换,太牛了
args[i] = JSON.parseObject(requestBody, parameter.getType());
// args[i]=convertToType(requestBody,parameter.getType()); //这个有问题
//TODO 本处处理泛型还需要细究去
// 请求体:{数组名:[]},像这样还要不要解析为链表(感觉解析为数组会不会不合适)
// 对应接口:/api/mall/settleAccounts
// 根据参数名映射或者注解中的value
// //处理泛型集合类型
// if(List.class.equals(parameter.getType())){
// // 获取泛型参数类型 T
// Type genericType = ((ParameterizedType) parameter.getParameterizedType()).getActualTypeArguments()[0];
// // 首先将整个 JSON 请求体解析为 JSONObject
// JSONObject jsonObject = JSON.parseObject(requestBody);//注意请求体中一般被{}包住了,是一个对象
// // 使用 JSON 工具解析为 List<T>
//// args[i] = JSON.parseArray(requestBody, (Class<?>) genericType);
// }else{
// //非泛型解析
// args[i] = JSON.parseObject(requestBody, parameter.getType());
//
// }
//到这里结束
} else if (parameter.isAnnotationPresent(RequestParam.class)) {
//TODO 待解决:处理数组
//DONE 待优化:当注解的value为空时,默认以参数名为主
// 解决失败:发现参数名为arg0
// 获取 @RequestParam 注解的 value,若为空,则使用参数名
String paramName = parameter.getAnnotation(RequestParam.class).value();
// 如果 value 为空,则默认使用参数名作为参数名
if (paramName.isEmpty()) {
paramName = parameter.getName(); // 获取参数名作为默认参数名
}
String paramValue = request.getParameter(paramName);
args[i] = convertToType(paramValue, parameter.getType());
} else {
String paramValue = request.getParameter(parameter.getName());
args[i] = convertToType(paramValue, parameter.getType());
}
}
return args;
}
/**
* 辅助方法:利用全局异常处理器进行异常处理
* @param response:响应
* @param exception:发生的异常
*/
private static void handleException(HttpServletResponse response, Exception exception) throws IOException {
Throwable cause = exception.getCause() != null ? exception.getCause() : exception;
// 找到能够处理的异常类型
Class<? extends Throwable> handledExceptionType = findCouldHandleException(cause);
// 如果找到对应的处理方法,则执行全局异常处理方法
if (handledExceptionType != null) {
Method handlerMethod = exceptionHandleMethod.get(handledExceptionType);
Object handlerInstance = restControllerAdvices.get(handledExceptionType);
try {
// 调用异常处理方法
Object result = handlerMethod.invoke(handlerInstance, cause);
// 将结果转换为 JSON 格式并写入响应
String jsonResponse = JSONObject.toJSONString(result);
response.getWriter().write(jsonResponse);
// response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.setStatus(HttpServletResponse.SC_OK);//这里是这么定义,这里面用result的code来定义
} catch (Exception e) {
e.printStackTrace();
// 如果异常处理失败,返回 500 错误
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error handling exception");
}
} else {
// 如果没有找到处理器,返回 500 错误
try {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unhandled exception");
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 辅助方法:循环查找父类,在全局异常处理器中找到能够处理的异常
* @param ex 需要查找的异常
* @return 能够处理的异常类,如果未找到则返回null
*/
private static Class<? extends Throwable> findCouldHandleException(Throwable ex){
Class<?> exceptionClass=ex.getClass();
while(exceptionClass!=null){
if(exceptionHandleMethod.containsKey(exceptionClass)){
return exceptionClass.asSubclass(Throwable.class); // 返回能处理的异常类
//确保返回的类型与方法声明的返回类型 Class<? extends Throwable> 一致。
}
exceptionClass = exceptionClass.getSuperclass(); // 向上查找父类
//将每次循环时的 exceptionClass 更新为其父类,逐层向上查找。
}
return null;
}
//TODO 待优化:思考下,如果为长整型怎么办
// 待看
/**
* 辅助方法:类型转换
* @param value
* @param type
* @return
*/
private static Object convertToType(String value, Class<?> type) {
//之前有问题,这里如果是long或者是自定义类型咋办?
// if (value == null) return null;
// if (type == Integer.class || type == int.class) {
// return Integer.parseInt(value);
// } else if (type == Double.class || type == double.class) {
// return Double.parseDouble(value);
// }
// return value;
if (value == null) return null;
try {
// 使用 ConvertUtils 转换基本类型和常见类型
//TODO 后续有时间添加时间类型
// 这个部分赶时间由GPT生成了
// 处理 LocalDateTime 类型
if (type == LocalDateTime.class) {
// 处理常见的日期格式,包括带时间和不带时间的日期
DateTimeFormatter formatter;
// 如果是带时间的格式 (例如 yyyy-MM-dd HH:mm:ss)
if (value.contains("T")) {
formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
}
// 如果是日期格式 (例如 yyyy-MM-dd),就加上默认时间
else if (value.matches("\\d{4}-\\d{2}-\\d{2}")) {
value += "T00:00:00"; // 加上默认时间
formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
} else {
throw new DateTimeParseException("Invalid date time format", value, 0);
}
return LocalDateTime.parse(value, formatter);
}
// 处理 java.util.Date 类型
if (type == Date.class) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.parse(value);
}
return ConvertUtils.convert(value, type);
} catch (Exception e) {
System.out.println("Type conversion error for value: " + value + ", target type: " + type.getName());
e.printStackTrace();
}
return value;
}
}
package com.wawu.common.IOC;
import com.wawu.common.annotation.IOC.Autowired;
import com.wawu.common.annotation.IOC.component.Component;
import com.wawu.common.annotation.IOC.component.RestController;
import com.wawu.common.annotation.controller.globalExceptionHandle.RestControllerAdvice;
import com.wawu.common.utils.AnnotationUtils;
import lombok.Getter;
import org.reflections.Reflections;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 实现IOC容器(控制反转和依赖注入)
* 表现:使用@Component及其相关注解注册bean对象,然后使用@Autowired进行注入
*/
public class ApplicationContext {
private Set<Class<? extends Annotation>> registerComponentAnnotations = new HashSet<>();//存放被@Component修饰的注解
private Map<Class<?>, Object> beanMap = new HashMap<>(); // key:类;value:实例;(单例模式)
//这个类似工厂模式
@Getter
private Set<Class<?>> restControllerClazzes = new HashSet<>();//单独再拿出被@RestController修饰的类,后续还需要注册路由
@Getter
private Set<Class<?>> restControllerAdviceClazzes=new HashSet<>();//存放所有的全局异常处理器
public ApplicationContext(String basePackage,String baseAnnotationPackage) {
// 1. 获取所有自定义注解,并筛选出需要注册的注解(这里逻辑上可能还有问题)
scanForRegisterAnnotations(baseAnnotationPackage);
System.out.println("@Component相关注解:"+registerComponentAnnotations);
// 2. 扫描并注册组件类
scanAndRegisterComponents(basePackage);
System.out.println("相关bean对象:"+beanMap);
// 3. 执行依赖注入
injectDependencies();
System.out.println("执行依赖注入成功。。。");
}
/**
* 扫描所有自定义注解,筛出所有@Component(包括本身)相关注解
* @param baseAnnotationPackage:注解基础包(只扫描这个包)
*/
private void scanForRegisterAnnotations(String baseAnnotationPackage) {
//把@Component本身加入
registerComponentAnnotations.add(Component.class);
//递归扫描所有被@Component修饰的注解
Reflections reflections = new Reflections(baseAnnotationPackage);
Set<Class<?>> annotations = reflections.getTypesAnnotatedWith(Component.class);
//TODO:可能有问题:是否能扫描到多层嵌套的注解,虽然这里不知道为什么是能够扫描到的
// System.out.println(annotations);
for (Class<?> annotation : annotations) {
//判断是否为注解
if(annotation.isAnnotation()){
//通过自定义的注解工具类递归判断是否被@Component修饰
if(AnnotationUtils.isAnnotationPresent(annotation,Component.class,true)){//注意:这里使用正常判断是不会递归判断,只能看最外层
registerComponentAnnotations.add((Class<? extends Annotation>) annotation);
}
}
}
}
/**
* 扫描同时实例化注册bean对象
* @param basePackage:当前需要扫描的包
*/
private void scanAndRegisterComponents(String basePackage) {
Reflections reflections = new Reflections(basePackage);//只扫描这个包
//1. 遍历跟@Component相关的注解
for(Class<? extends Annotation> registerComponentAnnotation:registerComponentAnnotations){
// System.out.println("当前注解:"+registerComponentAnnotation);
//2.获取被这个注解修饰的所有类同时进行判断是否需要实例话
Set<Class<?>> componentWithComponentAnnotated = reflections.getTypesAnnotatedWith(registerComponentAnnotation);//注意不会识别嵌套注解
// System.out.println(registerComponentAnnotation.getName()+":"+componentWithComponentAnnotated);
for (Class<?> clazz : componentWithComponentAnnotated) {
//如果为一个普通类,则进行注册实例化
if(!clazz.isInterface() && !clazz.isEnum() && !clazz.isPrimitive() && !clazz.isArray()){
//进行实例化注册
try {
Object bean = clazz.getDeclaredConstructor().newInstance();
beanMap.put(clazz, bean); // 将类及其实例包装对象加入beanMap
}catch (Exception e) {
e.printStackTrace();
}
//获取被@RestController修饰的类,后需要注册路由
//注意下:这里就不递归检查了
if(clazz.isAnnotationPresent(RestController.class)){
restControllerClazzes.add(clazz);
}else if (clazz.isAnnotationPresent(RestControllerAdvice.class)){//放全局异常处理器类
restControllerAdviceClazzes.add(clazz);
}
}
}
}
}
/**
* 执行依赖注入(简化后)
* 规则:需要为bean对象(被@Component修饰),会将被@Autowired修饰的属性注入
* - 普通类型:直接根据类型注入
* - 接口类型:当实现类只有一个时,会将这个注入
*
*/
private void injectDependencies() {
//遍历所有bean对象(就是被@Component修饰)
for(Map.Entry<Class<?>,Object> entry: beanMap.entrySet()){
Class<?> clazz = entry.getKey();
Object bean = entry.getValue();
//获取所有字段,检查是否有@Autowired注解
for(Field field:clazz.getDeclaredFields()){
if (field.isAnnotationPresent(Autowired.class)){
//1. 寻找其需要注入的依赖(实体)
//如果字段类型是接口,则需要根据接口类型获取实现类
//如果不是,则直接从注册的bean对象获取
Object dependency = null;
//获取字段类型
Class<?> fieldType=field.getType();
if(fieldType.isInterface()){
// 获取所有实现该接口的类
Set<Class<?>> implementors = getImplementors(fieldType);
//只能有一个实现,不然不好注入
if (implementors.size() == 1) {
//这里依然是从bean,而不是新建了
// Object dependency = implementors.iterator().next().getDeclaredConstructor().newInstance();//新建
dependency=beanMap.get(implementors.iterator().next());//TODO 探究:这里可以看看为什么不需要.getClass()
} else {
System.err.println("找到多个实现或者没有实现接口的类: " + fieldType.getName());
}
}else{
// 如果字段类型不是接口,直接从 beanMap 获取
dependency = beanMap.get(fieldType);
}
//2.注入(赋值)给这个字段
if (dependency != null) {
try {
field.setAccessible(true);
field.set(bean, dependency);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} else {
// 如果找不到匹配的依赖
System.err.println("无法注入依赖: " + fieldType.getName());
}
}
}
}
}
/**
* 获取接口类型的所有实现类
* @param interfaceType:接口的类型
* @return:当前接口所有的实现类
*/
private Set<Class<?>> getImplementors(Class<?> interfaceType) {
// Set<Class<?>> implementors = new HashSet<>();
// // 扫描所有已注册的 Bean,查找实现了该接口的类
// for (Class<?> clazz : beanMap.keySet()) {
// if (interfaceType.isAssignableFrom(clazz) && !clazz.equals(interfaceType)) {
// implementors.add(clazz);
// }
// }
// return implementors;
//这里像试试流,作用跟上面一样
return beanMap.keySet().stream()
.filter(clazz->{
return interfaceType.isAssignableFrom(clazz) && !clazz.equals(interfaceType);
//`isAssignableFrom` 是 Java 反射 API 中的一个方法,判断 interfaceType(通常为接口或父类)是否是 clazz 的超类或超接口,或 clazz 是否可以赋值给 interfaceType 类型的变量。
})
.collect(Collectors.toSet());
}
/**
* 获取bean对象
* @param clazz
* @return
* @param <T>
*/
public <T> T getBean(Class<T> clazz) {
// 从 beanMap 中查找指定类的实例
Object bean = beanMap.get(clazz);
// 如果找不到该类型的 Bean,可以考虑抛出异常或返回 null
if (bean == null) {
throw new IllegalArgumentException("No bean found for class: " + clazz.getName());
}
return clazz.cast(bean); // 返回转换为指定类型的 bean
}
}
4.2.2 数据库操作封装
4.2.2.1 功能概述
在本项目中,为了简化数据库操作并提高代码的可维护性,我们封装了一些数据库操作类和方法,主要涉及以下功能:
- 基本的数据库操作:
- 使用
SQLExcutorUtil
类封装了常见的数据库操作方法,如查询单个对象、查询多个对象、查询标量值、更新操作、批量更新等。这些方法通过调用QueryRunner
来执行 SQL 操作,简化了数据库的操作流程,调用者只需提供 SQL 语句和参数即可。 - 主要方法包括:
querySingle
:执行单条查询,返回一个对象。queryList
:执行查询,返回一个对象列表。queryScalar
:查询单个标量值(如计数或求和结果)。update
:执行更新、插入或删除操作。updateAndGetGeneratedKey
:执行插入操作,并返回生成的主键。batchUpdate
:执行批量更新操作。transaction
:执行事务操作,确保一组数据库操作原子性。
- 使用
- SQL构造器:
BaseSQLBuilder
类作为所有 SQL 构造器的基类,封装了构建 SQL 查询、更新语句的通用逻辑。它支持动态添加查询条件(WHERE
)、IN
、BETWEEN
条件等。- 继承
BaseSQLBuilder
类的具体 SQL 构造器:SelectSQLBuilder
:用于构建SELECT
查询语句,支持字段选择、动态条件、排序、分页等。UpdateSQLBuilder
:用于构建UPDATE
语句,支持动态设置更新字段和值,并自动拼接SET
和WHERE
子句。
通过这种封装方式,开发人员可以更加简便、清晰地进行数据库操作,且代码具有良好的可扩展性与可维护性。在此基础上,未来可进一步增强对其他数据库操作(如 DELETE
、INSERT
)的支持。
4.2.2.2 代码实现
核心思想:数据库操作的sql语句生成与执行分离
package com.wawu.common.utils;
import com.wawu.common.utils.DruidUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.*;
import java.util.List;
/**
* 设计初衷:
* 这个方法只管执行,其他都不管
* 对于使用者来说:
* 只需要要传入sql,参数,其他都不管
*/
public class SQLExcutorUtil {
//获取数据库连接
private static Connection getConnection() throws SQLException {
return DruidUtil.getConnection();
}
//把参数放在最后就不用新建数组了
//这里不使用泛型,达不到我想要的效果
/**
* 查询单个对象的方法
* @param sql SQL语句
* @param resultType 查询结果的类型
* @param params 查询参数
* @param <T> 返回的对象类型
* @return 返回查询的单个结果
* @throws SQLException
*/
public static <T> T querySingle(String sql, Class<T> resultType, Object... params) throws SQLException {
QueryRunner queryRunner = new QueryRunner();
try (Connection conn = getConnection()) {
// 使用 BeanHandler 处理单一结果
return queryRunner.query(conn, sql, new BeanHandler<>(resultType), params);
}
}
/**
* 查询多个对象的方法,返回一个列表
* @param sql SQL语句
* @param resultType 查询结果的类型
* @param params 查询参数
* @param <T> 返回的对象类型
* @return 返回查询的结果列表
* @throws SQLException
*/
public static <T> List<T> queryList(String sql, Class<T> resultType, Object... params) throws SQLException {
QueryRunner queryRunner = new QueryRunner();
try (Connection conn = getConnection()) {
// 使用 BeanListHandler 处理多个结果
return queryRunner.query(conn, sql, new BeanListHandler<>(resultType), params);
}
}
/**
* 返回单一的标量值
* @param sql
* @param resultType
* @param params
* @return
* @param <T>
* @throws SQLException
*/
public static <T> T queryScalar(String sql, Class<T> resultType, Object... params) throws SQLException {
QueryRunner queryRunner = new QueryRunner();
try (Connection conn = getConnection()) {
return queryRunner.query(conn, sql, new ScalarHandler<>(), params);
}
}
// 更新操作:INSERT、UPDATE、DELETE
public static int update(String sql, Object... params) throws SQLException {
QueryRunner queryRunner = new QueryRunner();
try (Connection conn = getConnection()) {
return queryRunner.update(conn, sql, params);
}
}
/**
* (GPT生成未细看)
* 执行更新语句,并返回生成的键值(insert)
* @param sql
* @param params
* @return
* @throws SQLException
*/
public static long updateAndGetGeneratedKey(String sql, Object... params) throws SQLException {
QueryRunner queryRunner = new QueryRunner();
try (Connection conn = getConnection()) {
// 执行插入语句并获取生成的主键(假设主键是自增的)
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
// 设置参数
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
// 执行更新
int affectedRows = ps.executeUpdate();
// 获取生成的主键
if (affectedRows > 0) {
try (ResultSet generatedKeys = ps.getGeneratedKeys()) {
if (generatedKeys.next()) {
return generatedKeys.getLong(1); // 返回生成的主键
}
}
}
return -1; // 没有生成主键
}
}
// 批量操作
public static int[] batchUpdate(String sql, Object[][] params) throws SQLException {
QueryRunner queryRunner = new QueryRunner();
try (Connection conn = getConnection()) {
return queryRunner.batch(conn, sql, params);
}
}
//TODO 这部分想应该不要放在这里最好,最好还是放在服务层
// 事务操作(可选)
public static void transaction(Runnable... operations) throws SQLException {
try (Connection conn = getConnection()) {
conn.setAutoCommit(false);
try {
for (Runnable operation : operations) {
operation.run(); // 执行操作
}
conn.commit(); // 提交事务
} catch (SQLException e) {
conn.rollback(); // 回滚事务
throw e;
}
}
}
}
package com.wawu.common.utils.SQLBuilder;
import java.util.ArrayList;
import java.util.List;
//TODO 细看:本处涉及到的代码都需要再次细看
//TODO 待完成:更新操作最好别用动态sql
/**
* 此处必须是要按顺序构造,不然肯定是有问题的
*/
public abstract class BaseSQLBuilder {
protected final StringBuilder sql;
protected final List<Object> params;
private boolean whereAdded = false; // 标记是否已添加 WHERE 条件
public BaseSQLBuilder(String baseQuery) {
this.sql = new StringBuilder(baseQuery);
this.params = new ArrayList<>();
}
/**
* 添加通用条件
*/
public BaseSQLBuilder addDynamicCondition(String fieldName, String operator, Object value) {
if (value != null) {
if (!whereAdded) { // 如果是第一个条件,添加 WHERE
sql.append(" WHERE ");
whereAdded = true;
} else { // 后续的条件添加 AND
sql.append(" AND ");
}
sql.append(fieldName).append(" ").append(operator).append(" ?");
params.add("LIKE".equalsIgnoreCase(operator) ? "%" + value + "%" : value);
}
return this;
}
/**
* 添加 IN 条件
*/
public BaseSQLBuilder addInCondition(String fieldName, List<?> values) {
if (values != null && !values.isEmpty()) {
if (!whereAdded) { // 如果是第一个条件,添加 WHERE
sql.append(" WHERE ");
whereAdded = true;
} else { // 后续的条件添加 AND
sql.append(" AND ");
}
sql.append(fieldName).append(" IN (");
for (int i = 0; i < values.size(); i++) {
sql.append("?");
if (i < values.size() - 1) sql.append(", ");
}
sql.append(")");
params.addAll(values);
}
return this;
}
/**
* 添加 BETWEEN 条件
*/
public BaseSQLBuilder addBetweenCondition(String fieldName, Object start, Object end) {
if (start != null && end != null) {
if (!whereAdded) { // 如果是第一个条件,添加 WHERE
sql.append(" WHERE ");
whereAdded = true;
} else { // 后续的条件添加 AND
sql.append(" AND ");
}
sql.append(fieldName).append(" BETWEEN ? AND ?");
params.add(start);
params.add(end);
}
return this;
}
/**
* 获取 SQL 语句
*/
public String getSQL() {
System.out.println(sql.toString());
System.out.println(params);
return sql.toString();
}
/**
* 获取参数数组
*/
public Object[] getParams() {
return params.toArray();
}
}
package com.wawu.common.utils.SQLBuilder;
import java.util.List;
public class SelectSQLBuilder extends BaseSQLBuilder {
public SelectSQLBuilder(String tableName) {
super("SELECT * FROM " + tableName); // 不再加上 WHERE 1=1
}
//TODO 待优化:折中了下,跟父类有点冗余了
// 严格来讲这里也不是继承
// 这里的问题:就是添加动态条件调用的是父类的,但是返回的父类,但是如果还需要排序之类的都不行了
@Override
public SelectSQLBuilder addDynamicCondition(String fieldName, String operator, Object value) {
super.addDynamicCondition(fieldName, operator, value);
return this;
}
@Override
public SelectSQLBuilder addInCondition(String fieldName, List<?> values) {
super.addInCondition(fieldName, values);
return this;
}
@Override
public SelectSQLBuilder addBetweenCondition(String fieldName, Object start, Object end) {
super.addBetweenCondition(fieldName, start, end);
return this;
}
//到这里结束
/**
* 自定义查询字段
*/
public SelectSQLBuilder selectFields(String... fields) {
if (fields != null && fields.length > 0) {
// 构建字段列表
String columns = String.join(", ", fields);
// 找到 SQL 中 "SELECT " 和 " FROM" 的位置,替换 SELECT * 为指定的列
int selectIndex = sql.indexOf("SELECT ") + "SELECT ".length(); // 找到 SELECT 后的位置
int fromIndex = sql.indexOf(" FROM"); // 找到 FROM 关键字的位置
if (selectIndex > 0 && fromIndex > selectIndex) {
sql.replace(selectIndex, fromIndex, columns); // 替换 * 为实际字段
}
}
return this;
}
/**
* 添加分组支持
*/
public SelectSQLBuilder addGroupBy(String... fields) {
if (fields != null && fields.length > 0) {
sql.append(" GROUP BY ").append(String.join(", ", fields));
}
return this;
}
/**
* 添加排序支持
*/
public SelectSQLBuilder addOrderBy(String sortField, String sortOrder) {
if (sortField != null && !sortField.isEmpty()) {
sql.append(" ORDER BY ").append(sortField).append(" ").append(sortOrder);
}
return this;
}
/**
* 添加分页支持
*/
public SelectSQLBuilder addLimit(int limit, int offset) {
sql.append(" LIMIT ").append(limit).append(" OFFSET ").append(offset);
return this;
}
}
package com.wawu.common.utils.SQLBuilder;
import java.util.Map;
//TODO 待优化:加上反引号``,去避免关键字冲突
public class UpdateSQLBuilder extends BaseSQLBuilder {
private boolean hasSetClause = false;
public UpdateSQLBuilder(String tableName) {
super("UPDATE " + tableName);
}
/**
* 添加更新字段和值到 SET 子句(实时拼接到 sql)
*/
public UpdateSQLBuilder setField(String fieldName, Object value) {
if (fieldName != null && value != null) {
if (!hasSetClause) {
sql.append(" SET ");
hasSetClause = true;
} else {
sql.append(", ");
}
sql.append(fieldName).append(" = ?");
params.add(value);
}
return this;
}
/**
* 添加多个更新字段和值
*/
public UpdateSQLBuilder setFields(Map<String, Object> fields) {
if (fields != null && !fields.isEmpty()) {
fields.forEach(this::setField);
}
return this;
}
/**
* 添加动态条件到 WHERE 子句
*/
@Override
public UpdateSQLBuilder addDynamicCondition(String fieldName, String operator, Object value) {
super.addDynamicCondition(fieldName, operator, value);
return this;
}
/**
* 获取最终 SQL
*/
@Override
public String getSQL() {
if (!hasSetClause) {
throw new IllegalStateException("No fields specified for update. Please use setField() or setFields().");
}
// 调用父类的 getSQL 方法,完成 WHERE 子句拼接
return super.getSQL();
}
}
4.3 系统部分功能实现
系统有点大,本处只选取部分功能
4.3.1 登录注册功能
界面:
代码实现:
- 前端部分:
<template>
<div class="ClientLogin" :style="{width:width+'px',height:height+'px'}">
<div class="content">
<h3>MoreMall</h3>
<div class="tag">
<span @click="setIndex(0)" :class="{selected:curIndex===0}">登录</span>
<span @click="setIndex(1)" :class="{selected:curIndex===1}">注册</span>
</div>
<div class="formBox" v-show="curIndex===0">
<input ref="account" type="text" placeholder="账号" />
<input ref="pwd" type="password" placeholder="密码" />
<button @click="login">登录</button>
</div>
<div class="formBox" v-show="curIndex===1">
<input ref="signEmail" type="text" placeholder="请输入注册的邮箱" />
<input ref="signName" type="text" placeholder="请输入昵称" />
<input ref="signPwd" type="password" placeholder="请输入密码" />
<input ref="signRecipient" type="text" placeholder="请输入收件人姓名" />
<input ref="signAddress" type="text" placeholder="请输入收件地址" />
<input ref="signPhone" type="text" placeholder="请输入联系电话" />
<button @click="signup">注册</button>
</div>
</div>
</div>
</template>
<script>
import { mapMutations } from 'vuex'
import {getClientSize} from '../../util/util';
import {login,signup} from '../../api/client';
export default {
name: 'ClientLogin',
computed:{
width(){
return getClientSize().width;
},
height(){
return getClientSize().height;
},
},
data () {
return {
curIndex:0
}
},
methods:{
...mapMutations({
setClientName: 'SET_CLIENT_NAME',
setClientToken: 'SET_CLIENT_TOKEN'
}),
setIndex(index){
if(index===this.curIndex){
return;
}
this.curIndex = index;
},
login(){
const account = this.$refs.account.value;
const pwd = this.$refs.pwd.value;
const res = login({
account:account,
pwd:pwd
});
res
.then((data)=>{
this.setClientName(data.name);
this.setClientToken(data.token);
this.$router.push('/');
})
.catch((e)=>{
alert(e)
})
},
signup(){
const res = signup({
email:this.$refs.signEmail.value,
nickname:this.$refs.signName.value,
pwd:this.$refs.signPwd.value,
recipient:this.$refs.signRecipient.value,
address:this.$refs.signAddress.value,
phone:this.$refs.signPhone.value,
});
res
.then((data)=>{
this.setClientName(data.name);
this.setClientToken(data.token);
this.$router.push('/');
})
.catch((e)=>{
console.error(e); // 打印错误对象到控制台
alert(e.message || '注册失败'); // 显示错误信息
})
}
}
}
</script>
<style scoped lang="less">
@import "../../assets/css/var.less";
.ClientLogin{
background-color: @bgColor;
position: relative;
.content{
width: 300px;
position: absolute;
top: 50%;
left: 50%;
margin-top: -260px;
margin-left: -150px;
text-align: center;
overflow: hidden;
h3{
color:@secondColor;
font-size: 50px;
}
.tag{
margin-top: 20px;
color:@fontDefaultColor;
margin-bottom: 20px;
span{
display: inline-block;
width: 50px;
text-align: center;
margin: 0 10px;
padding: 10px 0;
cursor: pointer;
}
.selected{
border-bottom: 2px solid @secondColor;
color:@secondColor
}
}
input{
border-radius: 0;
box-shadow: none;
background: #fff;
padding: 14px;
width: 80%;
border: 1px solid @borderColor;
margin-bottom: 10px;
}
button{
width: 90%;
background: @secondColor;
box-shadow: none;
border: 0;
border-radius: 3px;
line-height: 41px;
color: #fff;
cursor: pointer;
margin-top: 20px;
}
}
}
</style>
- 后端部分:
在登录功能中,用户提供 email
和 password
,后端通过 UsersDAO
查询数据库验证用户是否存在,如果不存在则抛出异常。如果用户存在,使用 BCrypt
对密码进行校验,如果验证成功,生成一个包含用户 ID 的 JWT 令牌返回给前端。这个令牌将用于后续的身份认证。
注册功能的流程类似,用户提交注册信息后,系统会先检查 email
和 nickname
是否已经注册。如果没有,后端会对密码进行加密(使用 BCrypt
)并保存用户数据到数据库。同时,系统生成一个 JWT 令牌,确保新用户可以立即登录。
在数据库交互部分,UsersDAO
通过构建动态 SQL 查询用户信息,支持根据 email
和 nickname
查找用户。如果用户未注册,执行插入操作,保存用户信息。密码存储时采用 BCrypt
加密算法,保证安全。
异常处理方面,通过自定义异常(如 LoginException
)来捕获并返回相关错误信息,确保在登录或注册过程中出现错误时能够准确反馈给前端。
package com.wawu.server.controller;
import com.wawu.common.annotation.IOC.Autowired;
import com.wawu.common.annotation.IOC.component.RestController;
import com.wawu.common.annotation.controller.mapping.PostMapping;
import com.wawu.common.annotation.controller.mapping.RequestMapping;
import com.wawu.common.annotation.controller.parameter.RequestBody;
import com.wawu.common.result.Result;
import com.wawu.pojo.dto.LoginDTO;
import com.wawu.pojo.entity.User;
import com.wawu.pojo.vo.LoginVO;
import com.wawu.server.service.UserService;
import java.sql.SQLException;
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
UserService userService;
/**
* 用户登录
* @param loginDTO
* @return
*/
@PostMapping("/login")
// @OptionsMapping("/login")
public Result<LoginVO> login(@RequestBody LoginDTO loginDTO) throws SQLException {
LoginVO loginVO=userService.login(loginDTO);
return Result.success(loginVO);
}
/**
* 注册用户(这里就不用dto了,跟数据库表都是对应的)
* @param user
* @return
*/
@PostMapping("/signup")
public Result<LoginVO> signup(@RequestBody User user) throws SQLException {
LoginVO loginVO=userService.signup(user);
return Result.success(loginVO);
}
}
package com.wawu.server.service;
import com.wawu.common.annotation.IOC.Autowired;
import com.wawu.common.annotation.IOC.component.Service;
import com.wawu.common.annotation.controller.parameter.RequestBody;
import com.wawu.common.constant.JwtClaimsConstant;
import com.wawu.common.constant.MessageConstant;
import com.wawu.common.exception.LoginException;
import com.wawu.common.property.JwtProperties;
import com.wawu.common.utils.JwtUtil;
import com.wawu.pojo.dto.LoginDTO;
import com.wawu.pojo.entity.User;
import com.wawu.pojo.vo.LoginVO;
import com.wawu.server.dao.UsersDAO;
import org.mindrot.jbcrypt.BCrypt;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserService {
@Autowired
private UsersDAO usersDAO;
//涉及到各种密码加密的一些东西
int saltRounds = 5; // 定义盐的复杂度
String salt = BCrypt.gensalt(saltRounds);
public LoginVO login(@RequestBody LoginDTO loginDTO) throws SQLException {
User user = usersDAO.getUser(User.builder().email(loginDTO.getAccount()).build());
//账号没有注册
if(user==null){
throw new LoginException(MessageConstant.LOGIN_EMAIL_NOT_REGISTER);
}
System.out.println(loginDTO);
System.out.println(user);
LoginVO loginVO=new LoginVO();
//校验密码
if(BCrypt.checkpw(loginDTO.getPwd(),user.getPwd())){
loginVO.setName(user.getNickname());
//JWT令牌存储内容:用户id
Map<String,Object> claims=new HashMap<>();//存放对应的的角色信息
claims.put(JwtClaimsConstant.USER_ID,user.getId());
loginVO.setToken(JwtUtil.createJWT(JwtProperties.secretKey,JwtProperties.ttl,claims));
System.out.println(JwtUtil.parseJWT(JwtProperties.secretKey,loginVO.getToken()));
}else{
throw new LoginException(MessageConstant.LOGIN_PWD_NOT_ERROR);
}
return loginVO;
}
/**
* 注册用户
* @param newUser
* @return
*/
public LoginVO signup(User newUser) throws SQLException {
//1. 判断是否已经被注册
User user = usersDAO.getUser(User.builder()
.email(newUser.getEmail()).build());
if(user!=null){
throw new LoginException(MessageConstant.LOGIN_EMAIL_HAD_REGISTER);
}
user = usersDAO.getUser(User.builder()
.nickname(newUser.getNickname()).build());
if(user!=null){
throw new LoginException(MessageConstant.LOGIN_NICKNAME_HAD_REGISTER);
}
//2. 插入数据
newUser.setCreatetime(LocalDateTime.now());
newUser.setUpdatetime(LocalDateTime.now());
newUser.setPwd(BCrypt.hashpw(newUser.getPwd(),salt));
newUser.setId(usersDAO.insert(newUser));
//3. 前端需要的返回数据
LoginVO loginVO=new LoginVO();
loginVO.setName(newUser.getNickname());
//JWT令牌存储内容:用户id
Map<String,Object> claims=new HashMap<>();//存放对应的的角色信息
claims.put(JwtClaimsConstant.USER_ID,newUser.getId());
loginVO.setToken(JwtUtil.createJWT(JwtProperties.secretKey,JwtProperties.ttl,claims));
System.out.println(JwtUtil.parseJWT(JwtProperties.secretKey,loginVO.getToken()));
return loginVO;
}
public static void main(String[] args) {
//测试下bcrypt加密
String pwd="123456";
String dbPwd="$2a$10$SYwliJBnXvX65qfauY.MROLoKFn5VjfzrwqIgbWGAcUGEl/1K3Vde";//数据库中的密码(经过加密)
int saltRounds = 5; // 定义盐的复杂度
String salt = BCrypt.gensalt(saltRounds);
//1.测试每次生成是否相同
System.out.println(BCrypt.hashpw(pwd,salt));
System.out.println(BCrypt.hashpw(pwd,salt));
System.out.println(BCrypt.hashpw(pwd,salt));
//测试结果为加密结果都相同
//2.校验密码
System.out.println(BCrypt.checkpw(pwd,dbPwd));
System.out.println(BCrypt.checkpw(pwd,dbPwd));
System.out.println(BCrypt.checkpw(pwd,dbPwd));
pwd="654321";
System.out.println(BCrypt.checkpw(pwd,dbPwd));
System.out.println(BCrypt.checkpw(pwd,dbPwd));
System.out.println(BCrypt.checkpw(pwd,dbPwd));
}
}
package com.wawu.server.dao;
import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.User;
import java.sql.SQLException;
import java.util.List;
@Repository
public class UsersDAO {
/**
* 条件查询用户(现在只是用邮箱查)
* @param userQuery
* @return
*/
public User getUser(User userQuery) throws SQLException {
SelectSQLBuilder selectSqlBuilder =new SelectSQLBuilder("users")
.addDynamicCondition("id","=",userQuery.getId())
.addDynamicCondition("email","=", userQuery.getEmail())
.addDynamicCondition("nickname","=",userQuery.getNickname());
User user = SQLExcutorUtil.querySingle(selectSqlBuilder.getSQL(), User.class, selectSqlBuilder.getParams());
return user;
}
/**
* 条件查询用户(现在只是用邮箱查)
* @param userQuery
* @return
*/
public List<User> getUsers(User userQuery) throws SQLException {
SelectSQLBuilder selectSqlBuilder =new SelectSQLBuilder("users")
.addDynamicCondition("email","=", userQuery.getEmail())
.addDynamicCondition("nickname","=",userQuery.getNickname());
List<User> users = SQLExcutorUtil.queryList(selectSqlBuilder.getSQL(), User.class, selectSqlBuilder.getParams());
return users;
}
//获取nickname
public String getNickNameById(Long Id) throws SQLException {
String sql="SELECT nickname FROM users WHERE id = ? LIMIT 1";
return SQLExcutorUtil.queryScalar(sql,String.class,Id);
}
public User getUserById(Long Id) throws SQLException {
SelectSQLBuilder sqlBuilder=new SelectSQLBuilder("users")
.addDynamicCondition("id","=",Id);
return SQLExcutorUtil.querySingle(sqlBuilder.getSQL(), User.class, sqlBuilder.getParams());
}
/**
* 插入用户信息
* @param user
* @return
*/
public Long insert(User user) throws SQLException {
String sql="INSERT INTO `users` (`id`,`email`,`pwd`,`nickname`,`sex`,`recipient`,`address`,`phone`,`headimg`,`updatetime`,`createtime`) VALUES (DEFAULT,?,?,?,0,?,?,?,DEFAULT,?,?);";
return SQLExcutorUtil.updateAndGetGeneratedKey(sql,user.getEmail(),user.getPwd(),user.getNickname(),user.getRecipient(),user.getAddress(),user.getPhone(),user.getUpdatetime(),user.getCreatetime());
}
public static void main(String[] args) throws SQLException {
UsersDAO usersDAO=new UsersDAO();
User userQuery=new User();
userQuery.setEmail("2790440179");
User user = usersDAO.getUser(userQuery);
System.out.println(user);
}
}
4.3.2 商品基础查询
页面:
代码实现:
- 前端部分:
<template>
<div class="Mall">
<header>
<div class="container clear">
<span class="title" @click="navTo('/mall')">MoreMall 一站式选购平台</span>
<NoticeList :notices="notices"/>
<div class="right" v-if="clientToken">
<span class="name">欢迎您,{{clientName}}</span>
<span @click="navTo('/mall/personal')">个人中心</span>
<span @click="logout">退出登录</span>
</div>
<div class="right" v-else>
<span @click="navTo('/login')">登录</span>
<span @click="navTo('/login')">注册</span>
</div>
</div>
</header>
<div class="content" :style="{minHeight:clientHeight+'px'}">
<div class="container">
<router-view></router-view>
</div>
<div class="fixedAd">
<img src="../../assets/img/index2.gif" alt="" />
<ul class="fixedList">
<li>
<i class="iconfont icon-collection_fill" />
<span>新人有礼</span>
</li>
<li>
<i class="iconfont icon-paixing-copy" />
<span>热门商品</span>
</li>
<li>
<i class="iconfont icon-fabulous" />
<span>用户反馈</span>
</li>
<li @click="backToTop" v-show="shouldShowBT">
<i class="iconfont icon-arrows-4-7" />
<span>回顶部</span>
</li>
</ul>
</div>
</div>
<div class="bottomInfo">
<div class="container">
<div class="service footerItem">
<p class="title">客户服务</p>
<span><i class="iconfont icon-people_fill" />在线客服</span>
<span><i class="iconfont icon-fabulous" />用户反馈</span>
</div>
<div class="intro footerItem">
<p class="title">何为MoreMall</p>
<p class="intro-p">MoreMall原创生活类电商品牌,秉承网易一贯的严谨态度,我们深入世界各地,从源头全程严格把控商品生产环节,力求帮消费者甄选到优质的商品</p>
<div>
关注我们:
<img src="http://yanxuan.nosdn.127.net/60068701f3a380911f237c26c91b39d0.png" alt=""/>
<img src="http://yanxuan.nosdn.127.net/031e783d7ee645b6096980d0bf83079b.png" alt=""/>
<img src="http://yanxuan.nosdn.127.net/0c8759a89cdbd7acf7f2921e6f0fad19.png" alt=""/>
</div>
</div>
<div class="code footerItem">
<p class="title">扫码下载APP</p>
<img src="../../assets/img/code.png" alt=""/>
<span>下载领1000元新人礼包</span>
</div>
</div>
</div>
<footer>
<div class="container">
<ul class="footerTop">
<li>
<img src="//yanxuan.nosdn.127.net/e6021a6fcd3ba0af3a10243b7a2fda0d.png" alt="" />
<span>30天无忧退换货</span>
</li>
<li>
<img src="//yanxuan.nosdn.127.net/e09c44e4369232c7dd2f6495450439f1.png" alt="" />
<span>满88元免邮费</span>
</li>
<li>
<img src="//yanxuan.nosdn.127.net/e72ed4de906bd7ff4fec8fa90f2c63f1.png" alt="" />
<span>XX品质保证</span>
</li>
</ul>
<div class="footerBottom">
<ul>
<li>关于我们</li>
<li>帮助中心</li>
<li>售后服务</li>
<li>配送与验收</li>
<li>商务合作</li>
<li>企业采购</li>
<li>开放平台</li>
<li>搜索推荐</li>
<li>友情链接</li>
</ul>
<p>XX公司版权所有 © 1996-2018 食品经营许可证:XXXXXXXXXXXXXXXXX</p>
</div>
</div>
</footer>
</div>
</template>
<script>
import { mapState,mapMutations } from 'vuex';
import NoticeList from '../../components/NoticeList';
import {getClientSize,backToTop} from '../../util/util';
export default {
name: 'Mall',
computed:{
...mapState([
'clientToken',
'clientName'
]),
},
components:{
NoticeList
},
data () {
return {
notices:['今日疯抢:牛皮防水男靴仅229元!直减2...','【福利】领1000元APP新人礼'],
clientHeight:getClientSize().height,
shouldShowBT:false
}
},
methods:{
...mapMutations({
clientLogout: 'CLIENT_LOGOUT',
}),
navTo(route){
this.$router.push(route)
},
logout(){
this.clientLogout();
this.$router.go(0);
},
backToTop(){
backToTop();
},
watchScrollTop(){
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
if(scrollTop>150){
this.shouldShowBT = true;
}else{
this.shouldShowBT = false;
}
}
},
mounted(){
document.addEventListener('scroll',this.watchScrollTop,false);
},
beforeDestroyed(){
document.removeEventListener('scroll',this.watchScrollTop,false);
}
}
</script>
<style scoped lang="less">
@import "../../assets/css/var.less";
.Mall{
width: 100%;
header{
width: 100%;
background-color: #333333;
height:38px;
color:@fontShallowColor;
user-select:none;
z-index: 10000;
position: absolute;
left: 0;
top: 0;
.container{
position: relative;
height:38px;
.title{
position: absolute;
left: 0;
display: inline-block;
height: 26px;
top: 50%;
margin-top: -13px;
line-height: 26px;
font-size: 14px;
cursor: pointer;
}
.NoticeListBox{
position: absolute;
left: 200px;
}
.right{
position: absolute;
right: 0;
display: inline-block;
height: 26px;
top: 50%;
margin-top: -13px;
line-height: 26px;
font-size: 14px;
span{
margin-left: 20px;
cursor: pointer;
}
.name{
cursor: default;
}
}
}
}
.content{
padding-top: 40px;
}
.fixedAd{
position: fixed;
right: 0;
top: 108px;
width: 72px;
img{
display: block;
width: 100%;
height: 154px;
}
.fixedList{
margin-top: 2px;
background-color: white;
width: 100%;
li{
width: 100%;
height: 80px;
text-align: center;
border-bottom: 1px solid @borderColor;
cursor: pointer;
padding-top: 12px;
i{
display: block;
font-size: 30px;
color:#666666;
}
span{
display: block;
font-size: 14px;
color:#666666;
margin-top: 4px;
}
&:last-child{
border-bottom: none;
}
&:hover{
i{
color:@thirdColor;
}
span{
color:@thirdColor;
}
}
}
}
}
.bottomInfo{
width: 100%;
height: 300px;
border-top: 1px solid @borderColor;
overflow: hidden;
margin-top: 80px;
.footerItem{
width: 33%;
height: 210px;
position: relative;
top: 45px;
display: inline-block;
text-align: center;
vertical-align: middle;
color:@fontDefaultColor;
.title{
color: @fontDeepColor;
margin-bottom: 30px;
}
}
.service{
border-right: 1px solid @borderColor;
span{
display: inline-block;
width: 80px;
height: 100px;
border:1px solid @borderColor;
text-align: center;
margin: 0 10px;
font-size: 14px;
cursor: pointer;
&:hover{
color:@thirdColor;
}
i{
display: block;
font-size: 30px;
margin-top: 20px;
margin-bottom: 10px;
}
}
}
.intro{
border-right: 1px solid @borderColor;
.intro-p{
font-size: 13px;
width: 300px;
margin: 0 auto;
text-align: left;
color:@fontDeepColor;
line-height: 1.8em;
}
div{
text-align: left;
font-size: 14px;
margin-left: 47px;
margin-top: 20px;
img{
margin: 0 6px;
display: inline-block;
vertical-align: middle;
}
}
}
.code{
img{
display: block;
margin: 0 auto;
}
span{
font-size: 12px;
color:@thirdColor;
margin-top: 10px;
display: block;
}
}
}
footer{
width: 100%;
height: 208px;
background-color: #414141;
color:white;
overflow: hidden;
.footerTop{
padding: 36px 0;
border-bottom: 1px solid #4f4f4f;
width: 100%;
li{
display: inline-block;
width: 33%;
text-align: center;
img{
vertical-align: middle;
}
span{
vertical-align: middle;
font-size: 18px;
margin-left: 10px;
}
}
}
.footerBottom{
color:@fontDefaultColor;
margin-top: 30px;
font-size: 13px;
text-align: center;
ul{
li{
display: inline-block;
cursor: pointer;
padding: 0 6px;
border-right: 2px solid @fontDefaultColor;
&:last-child{
border-right:none;
}
}
}
p{
margin-top: 5px;
}
}
}
}
</style>
- 后端部分:
package com.wawu.server.controller;
import com.wawu.common.annotation.IOC.Autowired;
import com.wawu.common.annotation.IOC.component.RestController;
import com.wawu.common.annotation.controller.mapping.DeleteMapping;
import com.wawu.common.annotation.controller.mapping.GetMapping;
import com.wawu.common.annotation.controller.mapping.PostMapping;
import com.wawu.common.annotation.controller.mapping.RequestMapping;
import com.wawu.common.annotation.controller.parameter.RequestBody;
import com.wawu.common.annotation.controller.parameter.RequestParam;
import com.wawu.common.result.Result;
import com.wawu.pojo.dto.AddOrderDTO;
import com.wawu.pojo.dto.AskGoodsMsgDTO;
import com.wawu.pojo.dto.SettleAccountsDTO;
import com.wawu.pojo.vo.GetGoodsCommentVO.GoodCommentVO;
import com.wawu.pojo.vo.GoodVO;
import com.wawu.pojo.vo.OrderVO.OrderVO;
import com.wawu.pojo.vo.getGoodsInfo.getGoodsInfoVO;
import com.wawu.pojo.vo.getGoodsMsg.getGoodsMsgVO;
import com.wawu.pojo.vo.searchGoodVO.goodVO;
import com.wawu.server.service.MallService;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.List;
@RestController
@RequestMapping("/api/mall")
public class MallController {
@Autowired
MallService mallService;
/**
* 通过商品类型获取商品列表
* @param typeId
* @return
* @throws SQLException
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
@GetMapping("/getGoodsByType")
public Result<List<GoodVO>> getGoodsByType(@RequestParam Long typeId) throws SQLException, InvocationTargetException, IllegalAccessException {
// public Result<List<GoodVO>> getGoodsByType(@RequestParam Long typeId) throws SQLException, InvocationTargetException, IllegalAccessException {
return Result.success(mallService.getGoodVOsByTypeId(typeId));
}
}
package com.wawu.server.service;
import com.wawu.common.annotation.IOC.Autowired;
import com.wawu.common.annotation.IOC.component.Service;
import com.wawu.common.constant.JwtClaimsConstant;
import com.wawu.common.constant.MessageConstant;
import com.wawu.common.exception.BaseException;
import com.wawu.common.property.JwtProperties;
import com.wawu.common.utils.JwtUtil;
import com.wawu.pojo.dto.AddOrderDTO;
import com.wawu.pojo.dto.AskGoodsMsgDTO;
import com.wawu.pojo.dto.CartDTO;
import com.wawu.pojo.entity.*;
import com.wawu.pojo.query.OrderQuery;
import com.wawu.pojo.vo.GetGoodsCommentVO.CommentVO;
import com.wawu.pojo.vo.GetGoodsCommentVO.GoodCommentVO;
import com.wawu.pojo.vo.GetGoodsCommentVO.UserVO;
import com.wawu.pojo.vo.GoodVO;
import com.wawu.pojo.vo.OrderVO.Goods;
import com.wawu.pojo.vo.OrderVO.OrderVO;
import com.wawu.pojo.vo.getGoodsInfo.getGoodsInfoVO;
import com.wawu.pojo.vo.getGoodsInfo.goodsDetailsVO;
import com.wawu.pojo.vo.getGoodsMsg.getGoodsMsgVO;
import com.wawu.pojo.vo.getGoodsMsg.replyVO;
import com.wawu.pojo.vo.searchGoodVO.goodVO;
import com.wawu.server.dao.*;
import io.jsonwebtoken.Claims;
import org.apache.commons.beanutils.BeanUtils;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Service
public class MallService {
@Autowired
GoodsDAO goodsDAO;
@Autowired
GoodsDetailsDAO goodsDetailsDAO;
/**
* 通过商品类型获取商品列表
* @param typeId:(-1时表示查全部)
* @return
* @throws SQLException
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
public List<GoodVO> getGoodVOsByTypeId(Long typeId) throws SQLException, InvocationTargetException, IllegalAccessException {
// List<Good> goods = goodsDAO.getGoodsByType(typeId==-1?null:typeId);
List<Good> goods=goodsDAO.getGoods(Good.builder()
.typeId(typeId==-1?null:typeId)//设置为空表示查全部
.build());
List<GoodVO> goodVOList=new ArrayList<>();
for(Good good:goods){
GoodVO goodVO=new GoodVO();
BeanUtils.copyProperties(goodVO,good);
//获取商品单价
float priceByGoodsId = goodsDetailsDAO.getPriceByGoodsId(good.getId());
goodVO.setPrice(priceByGoodsId);
goodVOList.add(goodVO);
}
return goodVOList;
}
}
package com.wawu.server.dao;
import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.exception.BaseException;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLBuilder.UpdateSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.Good;
import java.sql.SQLException;
import java.util.List;
@Repository
public class GoodsDAO {
/**
* 动态条件查询商品列表
* @param goodQuery
* @return
* @throws SQLException
*/
public List<Good> getGoods(Good goodQuery) throws SQLException {
SelectSQLBuilder selectSqlBuilder =new SelectSQLBuilder("goods")
.addDynamicCondition("typeId","=", goodQuery.getTypeId())
.addDynamicCondition("id","=",goodQuery.getId())
.addDynamicCondition("name","LIKE", goodQuery.getName())
.addOrderBy("createtime","DESC");
return SQLExcutorUtil.queryList(selectSqlBuilder.getSQL(),Good.class, selectSqlBuilder.getParams());
}
}
package com.wawu.server.dao;
import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.exception.BaseException;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLBuilder.UpdateSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.GoodDetail;
import java.sql.SQLException;
import java.util.List;
@Repository
public class GoodsDetailsDAO {
//TODO 下面这个接口还需要在细究下,就是一个商品对应多个商品详情这个怎么算?
/**
* 根据商品获取单价
* @param goodsId
* @return
* @throws SQLException
*/
public float getPriceByGoodsId(Long goodsId) throws SQLException {
String sql="SELECT unitPrice FROM goodsDetails WHERE goodsId = ? LIMIT 1";
return SQLExcutorUtil.queryScalar(sql,Float.class,goodsId);
}
}
4.3.3 订单条件查询
页面:
实现代码:
- 前端部分:
<template>
<div class="Orders">
<header class="clear">
<span>订单管理</span>
</header>
<div class="filters">
<input type="text" v-model="filters.userId" placeholder="用户ID">
<select v-model="filters.state">
<option value="">全部</option>
<option v-for="(label, index) in tags" :value="index-1" :key="index">{{label}}</option>
</select>
<input type="number" v-model="filters.minAmount" placeholder="最小金额">
<input type="number" v-model="filters.maxAmount" placeholder="最大金额">
<input type="date" v-model="filters.startDate">
<input type="date" v-model="filters.endDate">
<button @click="applyFilters">查询</button>
</div>
<!-- <Tag :tagList="tags" @indexChange="changeTag"/>-->
<div class="content">
<table class="ordersTable">
<thead>
<tr><th>订单号</th><th>用户昵称</th><th>收件人</th><th>收货地址</th><th>联系电话</th><th>商品</th><th>规格</th><th>购买数量</th><th>金额</th><th>订单状态</th><th>更新时间</th><th>操作</th></tr>
</thead>
<tbody>
<tr v-for="(item,index) in orderList" :key="'order'+item.id">
<td>{{item.id}}</td>
<td>{{item.user.nickname}}</td>
<td>{{item.user.name}}</td>
<td>{{item.user.address}}</td>
<td>{{item.user.phone}}</td>
<td>{{item.goods}}</td>
<td>{{item.spec}}</td>
<td>{{item.num}}</td>
<td>{{item.amount}}</td>
<!-- <td>{{item.state}}</td>-->
<td>{{ getOrderState(item.state) }}</td>
<td>{{item.time}}</td>
<td><button class="normal" @click="editOrder(item.id)">编辑</button><button class="delete" @click="deleteOrder(item.id)">删除</button></td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import {getOrders,deleteOrder} from '../../api/admin';
import Tag from '../../components/Tag';
export default {
name: 'Orders',
components:{
Tag
},
computed:{
},
data(){
return{
//各种查询条件
filters: {
userId: null,
state: null,
minAmount: null,
maxAmount: null,
startDate: null,
endDate: null,
},
tags:['全部','未付款','未发货','已发货','已到货'],
orderList:[]
}
},
methods:{
applyFilters() {
// 创建一个查询对象,动态去除空值或 null
const params = {};
if (this.filters.userId !== null && this.filters.userId !== "") {
params.userId = this.filters.userId;
}
if (this.filters.state !== null && this.filters.state !== "") {
params.state = this.filters.state;
}
if (this.filters.minAmount !== null && this.filters.minAmount !== "") {
params.minAmount = this.filters.minAmount;
}
if (this.filters.maxAmount !== null && this.filters.maxAmount !== "") {
params.maxAmount = this.filters.maxAmount;
}
if (this.filters.startDate !== null && this.filters.startDate !== "") {
params.startDate = this.filters.startDate;
}
if (this.filters.endDate !== null && this.filters.endDate !== "") {
params.endDate = this.filters.endDate;
}
// 发送请求时将 params 作为参数传递
getOrders(params)
.then((orders) => {
this.orderList = orders;
})
.catch((e) => {
alert(e);
});
},
// 用来转换状态数字到对应的文本
getOrderState(state) {
const stateMap = {
0: '未付款',
1: '未发货',
2: '已发货',
3: '已到货'
};
return stateMap[state] || '未知状态'; // 默认返回"未知状态"
},
changeTag(index){
const res = getOrders(index-1);
res
.then((orders)=>{
this.orderList = orders;
})
.catch((e)=>{
alert(e);
})
},
editOrder(id){
this.$router.push('/backstage/orders/'+id)
},
deleteOrder(id){
const res = deleteOrder(id);
res
.then(()=>{
alert('删除成功');
this.orderList.map((item,index)=>{
if(item.id===id){
this.orderList.splice(index,1);
}
})
})
.catch((e)=>{
alert(e);
})
}
},
mounted(){
this.changeTag(0);
}
}
</script>
<style scoped lang="less">
@import "../../assets/css/var.less";
.Orders{
header{
width: 100%;
height: 40px;
line-height: 40px;
span{
float: left;
}
}
.content{
width: 100%;
background-color: white;
position: relative;
top: -3px;
padding: 5px;
.ordersTable{
width: 100%;
th{
text-align: center;
}
tbody{
tr{
td{
max-width: 100px;
min-width: 30px;
text-align: center;
button{
display: block;
overflow: hidden;
margin-bottom: 5px;
}
}
}
}
}
}
.filters {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
input, select, button {
padding: 5px;
font-size: 14px;
}
}
}
</style>
- 后端部分:
此处看的是/orders
接口:
package com.wawu.server.controller;
import com.wawu.common.annotation.IOC.Autowired;
import com.wawu.common.annotation.IOC.component.RestController;
import com.wawu.common.annotation.controller.mapping.DeleteMapping;
import com.wawu.common.annotation.controller.mapping.GetMapping;
import com.wawu.common.annotation.controller.mapping.PostMapping;
import com.wawu.common.annotation.controller.mapping.RequestMapping;
import com.wawu.common.annotation.controller.parameter.RequestBody;
import com.wawu.common.annotation.controller.parameter.RequestParam;
import com.wawu.common.result.Result;
import com.wawu.pojo.dto.AddTypeDTO;
import com.wawu.pojo.dto.GoodDTO;
import com.wawu.pojo.dto.LoginDTO;
import com.wawu.pojo.entity.GoodDetail;
import com.wawu.pojo.entity.GoodType;
import com.wawu.pojo.query.OrderQuery;
import com.wawu.pojo.vo.GetGoodsInfoByGoodsIdVO;
import com.wawu.pojo.vo.GetOrders.GetOrderVO;
import com.wawu.pojo.vo.GoodVO;
import com.wawu.pojo.vo.LoginVO;
import com.wawu.pojo.vo.UserVO;
import com.wawu.server.service.AdminService;
import com.wawu.server.service.MallService;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.List;
@RestController
@RequestMapping("/api/admin")
public class AdminController {
@Autowired
AdminService adminService;
/**
* 管理员端登录
* @param loginDTO
* @return
*/
@PostMapping("/login")
public Result<LoginVO> login(@RequestBody LoginDTO loginDTO) throws SQLException {
return Result.success(adminService.login(loginDTO));
}
/**
* 获取所有用户信息
* @return
*/
@GetMapping("/allUser")
public Result<List<UserVO>> getAllUser() throws SQLException, InvocationTargetException, IllegalAccessException {
return Result.success(adminService.getAllUser());
}
@GetMapping("/getType")
public Result<List<GoodType>> getGoodTypes() throws SQLException {
return Result.success(adminService.getGoodTypes());
}
/**
* 添加商品类型
* @param addTypeDTO
* @return
*/
@PostMapping("/addType")
public Result addGoodType(@RequestBody AddTypeDTO addTypeDTO) throws SQLException {//这里只有一个属性,这里可以根据框架优化
adminService.addGoodType(addTypeDTO.getName());
return Result.success();
}
//TODO 看看吧:这里使用这个平替下
@Autowired
MallService mallService;
/**
* 通过商品类型id获取商品
* @param typeId
* @return
* @throws SQLException
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
@GetMapping("/getGoodsByType")
public Result<List<GoodVO>> getGoodVOsByTypeId(@RequestParam Long typeId) throws SQLException, InvocationTargetException, IllegalAccessException {
return Result.success(mallService.getGoodVOsByTypeId(typeId));
}
/**
* 添加商品
* @param goodDTO
* @return
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws SQLException
*/
@PostMapping("/addGoods")
public Result addGood(@RequestBody GoodDTO goodDTO) throws InvocationTargetException, IllegalAccessException, SQLException {
adminService.addGood(goodDTO);
return Result.success();
}
/**
* 更新商品
* @param goodDTO
* @return
*/
@PostMapping("/updateGoods")
public Result updateGood(@RequestBody GoodDTO goodDTO) throws InvocationTargetException, IllegalAccessException {
adminService.updateGood(goodDTO);
return Result.success();
}
/**
* 删除商品
* @param id
* @return
*/
@DeleteMapping("/deleteGoods")
public Result deleteGoodById(@RequestParam Long id) throws SQLException {
adminService.deleteGoodById(id);
return Result.success();
}
/**
* 获取商品详情
* @param id
* @return
*/
@GetMapping("/getGoodsInfo")
public Result<GetGoodsInfoByGoodsIdVO> getGoodsInfoByGoodsId(@RequestParam Long id) throws SQLException {
return Result.success(adminService.getGoodsInfoByGoodsId(id));
}
/**
* 添加商品详情(规格)
* @param goodDetail
* @return
* @throws SQLException
*/
@PostMapping("/addSpec")
public Result<GoodDetail> addSpec(@RequestBody GoodDetail goodDetail) throws SQLException {
return Result.success(adminService.addGoodDetail(goodDetail));
}
//TODO 后续有时间添加解析时间类型
//TODO 带完善:删除商品可能会出现异常,这里需要改一下
/**
* 动态查询订单
* @param userId
* @param state
* @param minAmount
* @param maxAmount
* @param startDate
* @param endDate
* @return
*/
@GetMapping("/orders")
public Result<List<GetOrderVO>> getOrders(
@RequestParam("userId") Long userId,
@RequestParam("state") Integer state,
@RequestParam("minAmount") Double minAmount,
@RequestParam("maxAmount") Double maxAmount,
@RequestParam("startDate") LocalDateTime startDate,
@RequestParam("endDate") LocalDateTime endDate
) throws SQLException, InvocationTargetException, IllegalAccessException {
// System.out.println(userId);
// System.out.println(state);
// System.out.println(minAmount);
// System.out.println(maxAmount);
// System.out.println(startDate);
// System.out.println(endDate);
return Result.success(adminService.getOrders(
OrderQuery.builder()
.userId(userId)
.state(state==null||state == -1 ?null:state)
.minAmount(minAmount)
.maxAmount(maxAmount)
.startDate(startDate)
.endDate(endDate)
.build()
));
}
}
package com.wawu.server.service;
import com.wawu.common.annotation.IOC.Autowired;
import com.wawu.common.annotation.IOC.component.Service;
import com.wawu.common.constant.JwtClaimsConstant;
import com.wawu.common.constant.MessageConstant;
import com.wawu.common.exception.BaseException;
import com.wawu.common.property.JwtProperties;
import com.wawu.common.utils.JwtUtil;
import com.wawu.pojo.dto.GoodDTO;
import com.wawu.pojo.dto.LoginDTO;
import com.wawu.pojo.entity.*;
import com.wawu.pojo.query.OrderQuery;
import com.wawu.pojo.vo.GetGoodsInfoByGoodsIdVO;
import com.wawu.pojo.vo.GetOrders.GetOrderUserVO;
import com.wawu.pojo.vo.GetOrders.GetOrderVO;
import com.wawu.pojo.vo.LoginVO;
import com.wawu.pojo.vo.UserVO;
import com.wawu.server.dao.*;
import org.apache.commons.beanutils.BeanUtils;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 处理管理员的服务
*/
@Service
public class AdminService {
@Autowired
GoodTypesDAO goodTypesDAO;
@Autowired
AdminsDAO adminsDAO;
@Autowired
UsersDAO usersDAO;
@Autowired
GoodsDAO goodsDAO;
@Autowired
GoodsDetailsDAO goodsDetailsDAO;
@Autowired
OrdersDAO ordersDAO;
public List<GoodType> getGoodTypes() throws SQLException {
return goodTypesDAO.getGoodTypes();
}
/**
* 管理员登录
* (此处数据库中没有加密)
* @param loginDTO
* @return
*/
public LoginVO login(LoginDTO loginDTO) throws SQLException {
//此处能够顺便校验密码
List<Admin> admins =adminsDAO.getAdmin(Admin.builder()
.account(loginDTO.getAccount())
.pwd(loginDTO.getPwd()).build());
if(admins ==null|| admins.isEmpty()){
throw new BaseException(MessageConstant.LOGIN_ACOUNT_NOT_EXIST);
}
Admin admin=admins.get(0);
LoginVO loginVO=new LoginVO();
loginVO.setName(admin.getName());
Map<String,Object> claims=new HashMap<>();
claims.put(JwtClaimsConstant.ADMIN_ID,admin.getId());
loginVO.setToken(JwtUtil.createJWT(JwtProperties.secretKey,JwtProperties.ttl,claims));
return loginVO;
}
//TODO 待优化此处没有判重
/**
* 添加商品类型
* @param typeName
*/
public void addGoodType(String typeName) throws SQLException {
goodTypesDAO.insert(typeName);
}
/**
* 获取所有的用户信息
* @return
*/
public List<UserVO> getAllUser() throws SQLException, InvocationTargetException, IllegalAccessException {
List<UserVO> userVOS=new ArrayList<>();
for(User user:usersDAO.getUsers(new User())){
UserVO userVO=new UserVO();
BeanUtils.copyProperties(userVO,user);
userVOS.add(userVO);
}
return userVOS;
}
//TODO 设置为事务
/**
* 添加商品(涉及到商品详情)
* @param goodDTO
*/
public void addGood(GoodDTO goodDTO) throws InvocationTargetException, IllegalAccessException, SQLException {
Good good=new Good();
BeanUtils.copyProperties(good,goodDTO);
good.setCreatetime(LocalDateTime.now());
good.setUpdatetime(LocalDateTime.now());
good.setId(goodsDAO.insert(good));
for (GoodDetail goodDetail : goodDTO.getSpecList()) {
goodDetail.setGoodsId(good.getId());
goodDetail.setCreateTime(LocalDateTime.now());
goodDetail.setUpdateTime(LocalDateTime.now());
goodsDetailsDAO.insert(goodDetail);
}
}
/**
* 更新商品
* @param goodDTO
*/
public void updateGood(GoodDTO goodDTO) throws InvocationTargetException, IllegalAccessException {
Good good=new Good();
BeanUtils.copyProperties(good,goodDTO);
good.setUpdatetime(LocalDateTime.now());
goodsDAO.update(good);
for (GoodDetail goodDetail : goodDTO.getSpecList()) {
goodDetail.setUpdateTime(LocalDateTime.now());
goodDetail.setCreateTime(null);//这里前端不知道为啥设置了
goodsDetailsDAO.update(goodDetail);
}
}
/**
* 通过商品类型删除商品,包括商品详情
* @param goodId
*/
public void deleteGoodById(Long goodId) throws SQLException {
goodsDAO.deleteById(goodId);
goodsDetailsDAO.deleteByGoodId(goodId);
}
/**
* 获取商品详情
* @param goodsId
* @return
*/
public GetGoodsInfoByGoodsIdVO getGoodsInfoByGoodsId(Long goodsId) throws SQLException {
List<Good> goods = goodsDAO.getGoods(Good.builder()
.id(goodsId).build());
if(goods==null||goods.isEmpty()){
throw new BaseException(MessageConstant.GOOD_NOT_EXIST);
}
Good good=goods.get(0);
GetGoodsInfoByGoodsIdVO getGoodsInfoByGoodsIdVO=new GetGoodsInfoByGoodsIdVO();
getGoodsInfoByGoodsIdVO.setGoods(good);
//获取商品详情(规格)
List<GoodDetail> goodDetails = goodsDetailsDAO.getGoodDetails(GoodDetail.builder()
.goodsId(goodsId).build());
getGoodsInfoByGoodsIdVO.setSpecs(goodDetails);
return getGoodsInfoByGoodsIdVO;
}
/**
* 添加商品详情
* @param goodDetail
* @return
*/
public GoodDetail addGoodDetail(GoodDetail goodDetail) throws SQLException {
goodDetail.setCreateTime(LocalDateTime.now());
goodDetail.setUpdateTime(LocalDateTime.now());
goodDetail.setId(goodsDetailsDAO.insert(goodDetail));
return goodDetail;
}
//TODO 这里其实有很多业务需要优化但是没时间了,不管了
/**
* 条件查询订单
* @param orderQuery
* @return
*/
public List<GetOrderVO> getOrders(OrderQuery orderQuery) throws SQLException, InvocationTargetException, IllegalAccessException {
List<GetOrderVO> getOrderVOS=new ArrayList<>();
for (Order order : ordersDAO.getOrders(orderQuery)) {
GetOrderVO getOrderVO=new GetOrderVO();
BeanUtils.copyProperties(getOrderVO,order);
getOrderVO.setTime(order.getUpdateTime());
//查询用户表
User user = usersDAO.getUser(User.builder()
.id(order.getUserId()).build());
if (user==null){
user=new User();
user.setNickname("已注销用户");
user.setRecipient("已注销用户");
user.setAddress("已注销用户");
user.setPhone("已注销用户");
}
GetOrderUserVO getOrderUserVO=new GetOrderUserVO();
BeanUtils.copyProperties(getOrderUserVO,user);
getOrderUserVO.setName(user.getRecipient());
getOrderVO.setUser(getOrderUserVO);
//查询商品详情表获取规格
List<GoodDetail> goodDetails = goodsDetailsDAO.getGoodDetails(GoodDetail.builder()
.id(order.getGoodsDetailId()).build());
GoodDetail goodDetail;
if(goodDetails==null||goodDetails.isEmpty()){
throw new BaseException(MessageConstant.GOOD_DETAIL_NOT_EXIST);
// goodDetail=new GoodDetail();
// goodDetail.setGoodsId();
}else{
goodDetail = goodDetails.get(0);
}
getOrderVO.setSpec(goodDetail.getSpecName());
//查询商品表获取商品名字
Good goodsById = goodsDAO.getGoodsById(goodDetail.getGoodsId());
getOrderVO.setGoods(goodsById.getName());
getOrderVOS.add(getOrderVO);
}
return getOrderVOS;
}
}
package com.wawu.server.dao;
import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.exception.BaseException;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLBuilder.UpdateSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.Order;
import com.wawu.pojo.query.OrderQuery;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@Repository
public class OrdersDAO {
/**
* 动态条件查询订单
* @param orderQuery
* @return
*/
public List<Order> getOrders(OrderQuery orderQuery) throws SQLException {
SelectSQLBuilder selectSqlBuilder = new SelectSQLBuilder("orders")
.addDynamicCondition("id","=",orderQuery.getId())
.addDynamicCondition("userId", "=", orderQuery.getUserId())
.addDynamicCondition("state", "=", orderQuery.getState())
.addBetweenCondition("updatetime",orderQuery.getStartDate(),orderQuery.getEndDate())
.addBetweenCondition("amount",orderQuery.getMinAmount(),orderQuery.getMaxAmount())
.addOrderBy("updatetime","DESC");
return SQLExcutorUtil.queryList(selectSqlBuilder.getSQL(), Order.class, selectSqlBuilder.getParams());
}
}
package com.wawu.server.dao;
import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.User;
import java.sql.SQLException;
import java.util.List;
@Repository
public class UsersDAO {
/**
* 条件查询用户(现在只是用邮箱查)
* @param userQuery
* @return
*/
public List<User> getUsers(User userQuery) throws SQLException {
SelectSQLBuilder selectSqlBuilder =new SelectSQLBuilder("users")
.addDynamicCondition("email","=", userQuery.getEmail())
.addDynamicCondition("nickname","=",userQuery.getNickname());
List<User> users = SQLExcutorUtil.queryList(selectSqlBuilder.getSQL(), User.class, selectSqlBuilder.getParams());
return users;
}
}
package com.wawu.server.dao;
import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.exception.BaseException;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLBuilder.UpdateSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.GoodDetail;
import java.sql.SQLException;
import java.util.List;
@Repository
public class GoodsDetailsDAO {
/**
* 动态条件查询商品详情表
* @param goodDetailQuery
* @return
*/
public List<GoodDetail> getGoodDetails(GoodDetail goodDetailQuery) throws SQLException {
SelectSQLBuilder selectSqlBuilder = new SelectSQLBuilder("goodsDetails")
.addDynamicCondition("id", "=", goodDetailQuery.getId())
.addDynamicCondition("goodsId", "=", goodDetailQuery.getGoodsId());
return SQLExcutorUtil.queryList(selectSqlBuilder.getSQL(),GoodDetail.class, selectSqlBuilder.getParams());
}
}
package com.wawu.server.dao;
import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.exception.BaseException;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLBuilder.UpdateSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.Good;
import java.sql.SQLException;
import java.util.List;
@Repository
public class GoodsDAO {
//获取商品资料
public Good getGoodsById(Long id) throws SQLException {
SelectSQLBuilder sqlBuilder=new SelectSQLBuilder("goods")
.addDynamicCondition("id","=", id)
.addOrderBy("createtime","DESC");
return SQLExcutorUtil.querySingle(sqlBuilder.getSQL(),Good.class,sqlBuilder.getParams());
}
}
4.4 页面总览
这里转存失败了,具体就看项目仓库里的,里面有图片
5. 小结
在开发本商城系统的过程中,我深刻体会到了团队协作、技术选型和架构设计对系统成功的关键作用。
首先,团队合作和沟通是整个项目顺利进行的基础。通过使用 Git 和 Gitee,我们能够高效地管理代码和协同开发。版本控制不仅帮助我们在不同开发者之间同步代码,还避免了冲突,保障了开发进度的顺利推进。与此同时,ApiFox 的使用确保了前后端接口的一致性,减少了因接口差异而导致的问题,提升了开发效率和准确性。
其次,项目的技术选型和架构设计让我深刻感受到了简洁与高效的重要性。在后端,我选择了 Servlet 技术与自定义注解,虽然看起来相对传统,但通过合理的设计和反射机制,简化了开发工作,减少了不必要的配置与依赖。这种思路让我意识到,很多时候简单的技术实现往往能解决复杂的业务需求,尤其是在团队项目中,技术的“简洁性”是保证高效协作的前提。
在前端部分,Vue.js 和 axios 的使用让我意识到,前后端分离的开发模式不仅提升了系统的可扩展性,还能使前端开发人员专注于界面与用户体验的优化,而后端则专注于业务逻辑。这种分工让开发过程更加高效,并且让整个系统的维护和扩展变得更加方便。
在安全性方面,JWT 和 BCrypt 技术的应用让我深刻理解到,安全是系统的基石。对用户信息的保护不仅仅是技术上的挑战,更是对用户信任的保证。通过加密算法保护密码和使用 token 验证身份的方式,增强了系统的安全性,也为用户提供了更加可靠的保障。
通过这次开发,我更加认识到在系统设计和开发过程中,灵活的技术选型、清晰的架构设计以及团队成员之间的有效合作,能共同促进一个高效、可扩展、易维护的系统的诞生。
arams());
return users;
}
}
```java
package com.wawu.server.dao;
import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.exception.BaseException;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLBuilder.UpdateSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.GoodDetail;
import java.sql.SQLException;
import java.util.List;
@Repository
public class GoodsDetailsDAO {
/**
* 动态条件查询商品详情表
* @param goodDetailQuery
* @return
*/
public List<GoodDetail> getGoodDetails(GoodDetail goodDetailQuery) throws SQLException {
SelectSQLBuilder selectSqlBuilder = new SelectSQLBuilder("goodsDetails")
.addDynamicCondition("id", "=", goodDetailQuery.getId())
.addDynamicCondition("goodsId", "=", goodDetailQuery.getGoodsId());
return SQLExcutorUtil.queryList(selectSqlBuilder.getSQL(),GoodDetail.class, selectSqlBuilder.getParams());
}
}
package com.wawu.server.dao;
import com.wawu.common.annotation.IOC.component.Repository;
import com.wawu.common.exception.BaseException;
import com.wawu.common.utils.SQLBuilder.SelectSQLBuilder;
import com.wawu.common.utils.SQLBuilder.UpdateSQLBuilder;
import com.wawu.common.utils.SQLExcutorUtil;
import com.wawu.pojo.entity.Good;
import java.sql.SQLException;
import java.util.List;
@Repository
public class GoodsDAO {
//获取商品资料
public Good getGoodsById(Long id) throws SQLException {
SelectSQLBuilder sqlBuilder=new SelectSQLBuilder("goods")
.addDynamicCondition("id","=", id)
.addOrderBy("createtime","DESC");
return SQLExcutorUtil.querySingle(sqlBuilder.getSQL(),Good.class,sqlBuilder.getParams());
}
}
4.4 页面总览
转存失败了具体看仓库地址里的吧,根据图片名搜索就能找到
#### 4.4.1 管理端





#### 4.4.2 购物端







5. 小结
在开发本商城系统的过程中,我深刻体会到了团队协作、技术选型和架构设计对系统成功的关键作用。
首先,团队合作和沟通是整个项目顺利进行的基础。通过使用 Git 和 Gitee,我们能够高效地管理代码和协同开发。版本控制不仅帮助我们在不同开发者之间同步代码,还避免了冲突,保障了开发进度的顺利推进。与此同时,ApiFox 的使用确保了前后端接口的一致性,减少了因接口差异而导致的问题,提升了开发效率和准确性。
其次,项目的技术选型和架构设计让我深刻感受到了简洁与高效的重要性。在后端,我选择了 Servlet 技术与自定义注解,虽然看起来相对传统,但通过合理的设计和反射机制,简化了开发工作,减少了不必要的配置与依赖。这种思路让我意识到,很多时候简单的技术实现往往能解决复杂的业务需求,尤其是在团队项目中,技术的“简洁性”是保证高效协作的前提。
在前端部分,Vue.js 和 axios 的使用让我意识到,前后端分离的开发模式不仅提升了系统的可扩展性,还能使前端开发人员专注于界面与用户体验的优化,而后端则专注于业务逻辑。这种分工让开发过程更加高效,并且让整个系统的维护和扩展变得更加方便。
在安全性方面,JWT 和 BCrypt 技术的应用让我深刻理解到,安全是系统的基石。对用户信息的保护不仅仅是技术上的挑战,更是对用户信任的保证。通过加密算法保护密码和使用 token 验证身份的方式,增强了系统的安全性,也为用户提供了更加可靠的保障。
通过这次开发,我更加认识到在系统设计和开发过程中,灵活的技术选型、清晰的架构设计以及团队成员之间的有效合作,能共同促进一个高效、可扩展、易维护的系统的诞生。