文章目录
项目介绍
介绍
一.环境搭建配置:
后端环境搭建 - 熟悉项目结构:
Common类:
constant :常量类
context:上下文
enumeration:枚举类
exception:异常处理类
json:jison转换类
properties:配置属性类
result:返回结果类、
utils:工具类
Pojo类:
二.前后端交互:
nginx反向代理
前端发请求然后通过负载均衡分配后端服务器
登录加密处理
使用springframework工具类: DigestUtils.md5DigestAsHex()
接口定义:前后端开发流程
Swagger: Knife4j依赖
Swagger接口生成文档步骤:
package com.sky.config;
import com.sky.interceptor.JwtTokenAdminInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
/**
* 配置类,注册web层相关组件
*/
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Autowired
private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
/**
* 注册自定义拦截器
*
* @param registry
*/
@Override
protected void addInterceptors(InterceptorRegistry registry) {
log.info("开始注册自定义拦截器...");
registry.addInterceptor(jwtTokenAdminInterceptor)
.addPathPatterns("/admin/**")
.excludePathPatterns("/admin/employee/login");
}
/**
* 通过knife4j生成接口文档
* @return
*/
@Bean
public Docket docket() {
log.info("准备生成接口文档");
ApiInfo apiInfo = new ApiInfoBuilder()
.title("苍穹外卖项目接口文档")
.version("2.0")
.description("苍穹外卖项目接口文档")
.build();
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo)
.select()
.apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
.paths(PathSelectors.any())
.build();
return docket;
}
/**
* 设置静态资源映射
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("开始设置静态资源映射");
registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
Swagger常用注解:
三.员工管理模块:
新增员工
后端新增功能:
//控制层
@RestController
@RequestMapping("/admin/employee")
@Slf4j
@Api(tags = "员工相关接口")
public class EmployeeController {
@PostMapping
@ApiOperation("新增员工")
public Result save( @RequestBody EmployeeDTO employeeDTO){
log.info("新增员工:{}",employeeDTO);
employeeService.save(employeeDTO);
return Result.success() ;
}
}
// 服务层
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
@Override
public void save(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
// 对象属性拷贝
BeanUtils.copyProperties(employeeDTO,employee);
// 设置账号的状态,默认正常状态 1 锁定0
employee.setStatus(StatusConstant.ENABLE);
// 默认密码
employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
// 设置当前记录的创建时间和修改时间
employee.setCreateTime(LocalDateTime.now());
employee.setUpdateTime(LocalDateTime.now());
// 设置当前记录创建人id和修改人id
// TODO 后期需要修改为当前登录用户id
employee.setCreateUser(10L);
employee.setUpdateUser(19L);
employeeMapper.insert(employee);
}
}
//服务层接口
public interface EmployeeService {
/**
* 员工登录
* @param employeeLoginDTO
* @return
*/
Employee login(EmployeeLoginDTO employeeLoginDTO);
/**
* 新增员工
* @param employeeDTO
*/
void save(EmployeeDTO employeeDTO);
}
//DTO前端和后端交互类
@Data
public class EmployeeDTO implements Serializable {
private Long id;
private String username;
private String name;
private String phone;
private String sex;
private String idNumber;
}
//数据库实体类
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String username;
private String name;
private String password;
private String phone;
private String sex;
private String idNumber;
private Integer status;
//@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
//@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
private Long createUser;
private Long updateUser;
}
//mapper
@Mapper
public interface EmployeeMapper {
/**
* 根据用户名查询员工
* @param username
* @return
*/
@Select("select * from employee where username = #{username}")
Employee getByUsername(String username);
@Insert("insert into employee (name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user,status)" +
"values" +
"(#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})")
void insert(Employee employee);
}
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理sql异常
* @param ex
* @return
*/
@ExceptionHandler
public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
// Duplicate entry 'zhangsan' for key 'employee.idx_username'
log.error("异常信息:{}",ex.getMessage());
String message =ex.getMessage();
// 如果包含"Duplicate entry" :键值对已存在的意思
if (message.contains("Duplicate entry")){
// 数组的第三个 “ ” 空格
String[] split = message.split(" ");
String username = split[2];
String msg = username + MessageConstant.ALREADY_EXISTS;
return Result.error(msg);
}else {
return Result.error(MessageConstant.UNKNOWN_ERROR);
}
}
}
新增员工: 获取创建人和修改人的id
在拦截器校验成功时 解析token中的登录用户的id值(token中会带登录用户的id):
分页查询:
在pom中导入pageheper依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
DTO类
@Data
public class EmployeePageQueryDTO implements Serializable {
//员工姓名
private String name;
//页码
private int page;
//每页显示记录数
private int pageSize;
}
application.yml配置:
mybatis:
#mapper配置文件
//配置扫描mapper地址
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.sky.entity
configuration:
#开启驼峰命名
map-underscore-to-camel-case: true
EmployeeMapper类
public interface EmployeeMapper {
Page<Employee> pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
}
EmployeeMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.EmployeeMapper">
<select id="pageQuery" resultType="com.sky.entity.Employee">
select * from employee
<where>
<if test="name !=null and name != ''">
//CONCAT函数 1.功能 将多个字符串连接成一个字符串
//返回结果为连接参数产生的字符串。如有任何一个参数为NULL ,则返回值为 NULL。
// mysql> SELECT CONCAT('张三','李四','王五');
//result> 张三李四王五
//——————————————————————————————————————————————
//mysql> SELECT CONCAT('张三','李四',NULL);
//result> NUll
and name like concat('%',#{name},'%')
</if>
</where>
order by create_time desc
</select>
</mapper>
server层
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
private EmployeeMapper employeeMapper;
@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO){
PageHelper.startPage(employeePageQueryDTO.getPage(),employeePageQueryDTO.getPageSize());
Page<Employee> page = employeeMapper.pageQuery(employeePageQueryDTO);
long total = page.getTotal();
List<Employee> records = page.getResult();
return new PageResult(total,records);
}
}
server接口层
/**
* 页码分页
* @param employeePageQueryDTO
* @return
*/
public interface EmployeeService {
PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO);
}
Controller控制层
@RestController
@RequestMapping("/admin/employee")
@Slf4j
@Api(tags = "员工相关接口")
public class EmployeeController {
@GetMapping("/page")
@ApiOperation("员工分页查询")
public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){
log.info("员工分页查询,参数为 {}:",employeePageQueryDTO);
PageResult pageResult= employeeService.pageQuery(employeePageQueryDTO);
return Result.success(pageResult);
}
}
日期格式化:
/**
* 扩展Spring MVC框架的消息转化器
* @param converters
*/
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Autowired
private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
// 创建一个消息转换器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
// 需要为消息转换器设置一个对象转换器,对象转换器可以将java对象序列化为json数据
converter.setObjectMapper(new JacksonObjectMapper());
// 将自己的消息转化器加入到容器中
// 0:代表顺序排在第一位最先调用
converters.add(0,converter);
}
}
/**
* 对象映射器:基于jackson将Java对象转为json,或者将json转为Java对象
* 将JSON解析为Java对象的过程称为 [从JSON反序列化Java对象]
* 从Java对象生成JSON的过程称为 [序列化Java对象到JSON]
*/
public class JacksonObjectMapper extends ObjectMapper {
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
//public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm";
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
public JacksonObjectMapper() {
super();
//收到未知属性时不报异常
this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
//反序列化时,属性不存在的兼容处理
this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
SimpleModule simpleModule = new SimpleModule()
.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))
.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注册功能模块 例如,可以添加自定义序列化器和反序列化器
this.registerModule(simpleModule);
}
}
员工账号启用和禁用状态设置:
Controller层
@RestController
@RequestMapping("/admin/employee")
@Slf4j
@Api(tags = "员工相关接口")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@Autowired
private JwtProperties jwtProperties;
/**
* 启用禁用员工账号
* @param status
* @param id
* @return
*/
@PostMapping("/status/{status}")
@ApiOperation("启用禁用员工账号")
public Result startOrStop(@PathVariable Integer status ,Long id){
log.info("启用禁用员工账号:{},{}",status,id);
employeeService.startOrStop(status,id);
return Result.success();
}
}
Service层
@Override
public void startOrStop(Integer status, Long id) {
// update employee set status = ? where id = ?
// Employee employee = new Employee();
// employee.setStatus(status);
// employee.setId(id);
Employee employee = Employee.builder()
.status(status)
.id(id)
.build();
employeeMapper.update(employee);
}
EmployeeMapper 类
@Mapper
public interface EmployeeMapper {
void update(Employee employee);
}
EmployeeMapper,xml
<!-- 配置类做了扫描 可以扫描的到类型别名 不用写完整地址-->
<update id="update" parameterType="Employee">
update employee
<set>
<if test="name != null">name = #{name},</if>
<if test="username != null">username = #{username},</if>
<if test="password != null">password = #{password},</if>
<if test="phone != null">phone = #{phone},</if>
<if test="sex != null">sex = #{sex},</if>
<if test="idNumber != null">id_Number = #{idNumber},</if>
<if test="updateTime != null">update_Time = #{updateTime},</if>
<if test="updateUser != null">update_User = #{updateUser},</if>
<if test="status != null">status = #{status},</if>
</set>
where id=#{id}
</update>
编辑员工(根据id查询):
查询id显示人员的目的是:
点击修改按钮页面保留原来的数据 (数据有内容不是空白的)
Controller层
/**
* 根据id查询
* @param id
* @return
*/
@GetMapping("/{id}")
@ApiOperation("根据id查询员工信息")
public Result<Employee> getById(@PathVariable Long id){
log.info("id查询员工信息:{}",id);
Employee employee = employeeService.getById(id);
return Result.success(employee);
}
/**
* 编辑员工信息
* @param employeeDTO
*/
@PutMapping
@ApiOperation("修改员工信息")
public Result<Employee> update(@RequestBody EmployeeDTO employeeDTO){
log.info("编辑员工信息:{}",employeeDTO);
employeeService.update(employeeDTO);
return Result.success();
}
EmployeeService接口和和Impl 实现类
接口----------------------------------------------
/**
* 编辑员工信息
* @param employeeDTO
*/
void update(EmployeeDTO employeeDTO);
/**
* 根据id查询
* @param id
* @return
*/
Employee getById(Long id);
实现类--------------------------------------
/**
* 根据id查询
* @param id
* @return
*/
@Override
public Employee getById(Long id){
Employee employee = employeeMapper.getById(id);
employee.setPassword("****");
return employee;
}
/**
* 编辑员工信息
* @param employeeDTO
*/
@Override
public void update(EmployeeDTO employeeDTO){
Employee employee = new Employee();
BeanUtils.copyProperties(employeeDTO,employee);
employee.setUpdateUser(BaseContext.getCurrentId());
employee.setUpdateTime(LocalDateTime.now());
employeeMapper.update(employee);
}
Mapper
/**
* 根据主键动态修改属性 修改写在xml里
* @param employee
*/
void update(Employee employee);
/**
* 根据id查询
* @param id
* @return
*/
@Select("select * from employee where id=#{id}")
Employee getById(Long id);
四.分类模块
导入分类模块功能代码
然后把类导进来就好。。。。。。。
公共字段填充
AutoFill注解类
/**
* 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
*
* @author ayyy
* @date 2024/03/13 18:26
*/
//@Target:当前自定义注解加在什么位置 ElementType.METHOD:加在方法上
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
// 数据库操作类型: UPDATE INSERT
OperationType value();
}
AutoFillAspect类
/**
* 自定义切面,实现公共字段自动填充处理逻辑
*
* @author ayyy
* @date 2024/03/13 18:32
*/
//切面注解
@Aspect
//bean注解
@Component
//日志注解
@Slf4j
public class AutoFillAspect {
/**
* 切入点
*/
/* 可以使用通配符描述切入点,快速描述
* :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution(public *(代表返回值任意) com.itheima.*.UserService.find*(*(一个任意参数)))
匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
.. :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(public User com..UserService.findById(..(多个任意)))
匹配com包下的任意包中的UserService类或接口中所有名称为findByld的方法
+ :专用于匹配子类类型:
execution(*(任意返回值) *(任意包)..*Service+(类u或接口的子类).*(..))
*/
// execution:指定拦截位置 ,第一个*代表 @annotation:自定义注解位置
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){
}
/**
* 前置通知,在通知中进行公共字段的赋值
*/
@Before("autoFillPointCut()")
// 连接点:JoinPoint (获取 参数类型 ,值)
public void autoFill(JoinPoint joinPoint){
log.info("开始进行公共字段自动填充...");
// 获取当前被拦截的方法上的数据库操作类型
MethodSignature signature = (MethodSignature)joinPoint.getSignature();//获得签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType operationType = autoFill.value();//获得数据库操作类型
// 获取到当前被拦截的方法参数--实体赋值
Object[] args = joinPoint.getArgs(); //获取所有参数
if (args == null || args.length == 0){
return;
}
Object entity = args[0];
// 准备赋值数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
// 根据当前不同的操作类型,为对应的属性通过反射来赋值
if (operationType == OperationType.INSERT){
// 为4个公共字段赋值
try {
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setCreateTime.invoke(entity,now);
setCreateUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}else if (operationType == OperationType.UPDATE){
// 为2个公共字段赋值
try {
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
在mapper类中加入AutoFill注解
/**
* 根据主键动态修改属性
* @param employee
*/
// 自定义注解加入才能通知到
@AutoFill(value = OperationType.UPDATE)
void update(Employee employee);
/**
* 新增员工
* @param
* @return
*/
@Insert("insert into employee (name, username, password, phone, sex, id_number, create_time, update_time, create_user, update_user,status)" +
"values" +
"(#{name},#{username},#{password},#{phone},#{sex},#{idNumber},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})")
@AutoFill(value = OperationType.INSERT)
void insert(Employee employee);
常用类
/**
* 数据库操作类型
*/
public enum OperationType {
/**
* 更新操作
*/
UPDATE,
/**
* 插入操作
*/
INSERT
}
五.菜品模块
新增菜品
新增菜品类中有新增口味类所以要分开写:
Controller层
@RestController
@RequestMapping("/admin/dish")
@Slf4j
@Api(tags = "菜品相关接口")
public class DishController {
@Autowired
private DishService dishService;
@PostMapping()
@ApiOperation(value = "新增菜品")
public Result save(@RequestBody DishDTO dishDTO){
log.info("新增菜品:{}",dishDTO);
dishService.saveWithFlavor(dishDTO);
return Result.success();
}
}
DishService层
public interface DishService {
/**
* 新增菜品对应口味数据
* @param dishDTO
*/
public void saveWithFlavor(DishDTO dishDTO);
}
private DishFlavorMapper dishFlavorMapper;
@Override
@Transactional
public void saveWithFlavor(DishDTO dishDTO) {
// 向菜品表插入1条数据
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO,dish);
dishMapper.insert(dish);
//在mapper中设置返回主键值
//获取inset语句生成的主键值:id
Long dishId = dish.getId();
//向口味表插入n条数据
List<DishFlavor> flavors = dishDTO.getFlavors();
if (!flavors.isEmpty() && flavors.size()>0){
flavors.forEach(dishFlavor -> {
dishFlavor.setDishId(dishId);
});
dishFlavorMapper.insertBatch(flavors);
}
}
}
Mapper层:
DishMapper
@Mapper
public interface DishMapper {
/**
* 根据分类id查询菜品数量
* @param categoryId
* @return
*/
@Select("select count(id) from dish where category_id = #{categoryId}")
Integer countByCategoryId(Long categoryId);
/**
* 插入菜品数据
* @param dish
*/
@AutoFill(value = OperationType.INSERT)
void insert(Dish dish);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.DishMapper">
<!-- useGeneratedKeys="true":需要主键值返回 keyProperty="id":返回主键值id -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into dish (name, category_id, price, image, description, status, create_time, update_time, create_user, update_user)
VALUES
(#{name}, #{categoryId}, #{price}, #{image}, #{description}, #{status}, #{createTime}, #{updateTime}, #{createUser},
#{updateUser})
</insert>
</mapper>
DishFlavorMapper
@Mapper
public interface DishFlavorMapper {
void insertBatch(List<DishFlavor> flavors);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.DishFlavorMapper">
<insert id="insertBatch">
insert into dish_flavor (dish_id, name, value) VALUES
<foreach collection="flavors" item="df" separator=",">
(#{df.dishId},#{df.name},#{df.value})
</foreach>
</insert>
</mapper>
阿里云上传文件
设置配置类
@Component
@ConfigurationProperties(prefix = "sky.alioss")
@Data
public class AliOssProperties {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
}
配置yml文件:
alioss:
endpoint: ${sky.alioss.endpoint}
access-key-id: ${sky.alioss.access-key-id}
access-key-secret: ${sky.alioss.access-key-secret}
bucket-name: ${sky.alioss.bucket-name}
配置环境变量:
alioss:
endpoint: oss-cn-beijing.aliyuncs.com
access-key-id: LTAI5tQ2s7fkHymv9xHjLcFL
access-key-secret: 70MRFuxAXcw9bglrCs3A1QujopPdeW
bucket-name: ayyy-object-photo
阿里云工具类:
需要自己配置文件访问路径
@Data
@AllArgsConstructor
@Slf4j
public class AliOssUtil {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
/**
* 文件上传
*
* @param bytes
* @param objectName
* @return
*/
public String upload(byte[] bytes, String objectName) {
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
try {
// 创建PutObject请求。
ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(bytes));
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
//文件访问路径规则 https://BucketName.Endpoint/ObjectName
StringBuilder stringBuilder = new StringBuilder("https://");
stringBuilder
.append(bucketName)
.append(".")
.append(endpoint)
.append("/")
.append(objectName);
log.info("文件上传到:{}", stringBuilder.toString());
return stringBuilder.toString();
}
}
创建配置工具类对象:
/**
* 配置类,用于创建AliOssUtil对象
*
* @author ayyy
* @date 2024/03/15 10:55
*/
@Configuration
@Slf4j
public class OssConfiguration {
// @Bean 项目启动调用方法 把对象创建 交给spring容器管理
@Bean
// @ConditionalOnMissingBean,它是修饰bean的一个注解,主要实现的是,当你的bean被注册之后,如果而注册相同类型的bean,就不会成功,它会保证你的bean只有一个,即你的实例只有一个。
//当没有这个bean在去创建(保证创建一次)
@ConditionalOnMissingBean
public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){
log.info("开始创建阿里云文件上传工具类对象:{}",aliOssProperties);
return new AliOssUtil(aliOssProperties.getEndpoint(),
aliOssProperties.getAccessKeyId(),
aliOssProperties.getAccessKeySecret(),
aliOssProperties.getBucketName());
}
}
CommonController(上传文件接口):
@RestController
@RequestMapping("/admin/common")
@Api(tags = "通用接口")
@Slf4j
public class CommonController {
@Autowired
private AliOssUtil aliOssUtil;
@PostMapping("/upload")
@ApiOperation("文件上传")
public Result<String> upload(MultipartFile file){
log.info("文件上传:{}",file);
try {
//原始文件名
String originalFilename = file.getOriginalFilename();
//截取原始文件名的后缀 例如:dfdfdf.png
String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
//构造新文件名称
String objectName = UUID.randomUUID().toString() + extension;
//文件的请求路径
//objectName 防止文件名称覆盖 所以重命名
String filePath = aliOssUtil.upload(file.getBytes(), objectName);
return Result.success(filePath);
} catch (IOException e) {
log.error("文件上传失败:{}",e);
}
return null;
}
}
菜品分页查询
Controller层
@RestController
@RequestMapping("/admin/dish")
@Slf4j
@Api(tags = "菜品相关接口")
public class DishController {
@Autowired
private DishService dishService;
@GetMapping("/page")
@ApiOperation("菜品分页查询")
public Result<PageResult> page(DishPageQueryDTO dishPageQueryDTO){
log.info("菜品分页查询:{}",dishPageQueryDTO);
PageResult pageResult = dishService.pageQuery(dishPageQueryDTO);
return Result.success(pageResult);
}
}
Service层
public interface DishService {
/**
* 菜品分页查询
* @param dishPageQueryDTO
* @return
*/
PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO);
@Service
@Slf4j
public class DishServiceImpl implements DishService {
@Autowired
private DishMapper dishMapper;
@Autowired
private DishFlavorMapper dishFlavorMapper;
/**
* 菜品分页查询
* @param dishPageQueryDTO
* @return
*/
@Override
public PageResult pageQuery(DishPageQueryDTO dishPageQueryDTO) {
PageHelper.startPage(dishPageQueryDTO.getPage(), dishPageQueryDTO.getPageSize());
Page<DishVO> page = dishMapper.pageQuery(dishPageQueryDTO);
return new PageResult(page.getTotal(),page.getResult());
}
}
Mapper层:
@Mapper
public interface DishMapper {
/**
* 菜品分页查询
* @param dishPageQueryDTO
* @return
*/
Page<DishVO> pageQuery(DishPageQueryDTO dishPageQueryDTO);
}
<select id="pageQuery" resultType="com.sky.vo.DishVO">
select d.* , c.name as categoryName from dish d left outer join category c on d.category_id = c.id
<where>
<if test="name != null">
and d.name like concat('%',#{name},'%')
</if>
// 注意: <!-- 这里id用错表了导致查询不到-->
// <!-- <if test="categoryId != null">-->
//<!-- and d.id =#{categoryId}-- 要用categoryId>
<if test="categoryId != null">
and d.category_id = #{categoryId}
</if>
<if test="status != null">
and d.status = #{status}
</if>
</where>
order by d.create_time desc
</select>
删除菜品
菜品删除功能
Controller层
@DeleteMapping()
@ApiOperation("菜品删除功能")
public Result delete(@RequestParam List<Long> ids){
log.info("菜品删除:{}",ids);
dishService.deleteBatch(ids);
return Result.success();
}
Service接口
public interface DishService {
/**
* 菜品批量删除
* @param ids
*/
void deleteBatch(List<Long> ids);
}
Service层
@Override
@Transactional
public void deleteBatch(List<Long> ids) {
//可以一次删除一个菜品,也可以批量删除菜品
//起售中的菜品不能删除
for (Long id : ids) {
Dish dish = dishMapper.getById(id);
if (dish.getStatus().equals(StatusConstant.ENABLE)){
throw new DeletionNotAllowedException(MessageConstant.DISH_ON_SALE);
}
}
//被套餐关联的菜品不能删除
List<Long> setmealIdsDishIds = setmealDishMapper.getSetmealIdsDishIds(ids);
if (!setmealIdsDishIds.isEmpty() && setmealIdsDishIds.size() > 0){
throw new DeletionNotAllowedException(MessageConstant.CATEGORY_BE_RELATED_BY_SETMEAL);
}
//删除菜品后,关联的口味数据也需要删除掉
for (Long id : ids) {
dishMapper.deleteById(id);
dishFlavorMapper.deleteBatch(id);
}
}
Mapper层:
DishMapper
/**
* 根据id查询
* @param id
* @return
*/
@Select("select * from dish where id=#{id} ")
Dish getById(Long id);
/**
* 根据id删除
* @param ids
*/
@Delete("delete from dish where id=#{id} ")
void deleteById(Long ids);
DishFlavorMapper
@Mapper
public interface DishFlavorMapper {
/**
* 根据菜品id删除
* @param dishId
*/
@Delete("delete from dish_flavor where dish_id=#{dishId}")
void deleteBatch(Long dishId);
}
SetmealDishMapper
Mapper
public interface SetmealDishMapper {
/**
* 根据多个菜品id查询套餐id
* @param dishIds
* @return
*/
List<Long> getSetmealIdsDishIds(List<Long> dishIds);
}
Mapper.xml:
DishFlavorMapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.DishFlavorMapper">
<insert id="insertBatch">
insert into dish_flavor (dish_id, name, value) VALUES
<foreach collection="flavors" item="df" separator=",">
(#{df.dishId},#{df.name},#{df.value})
</foreach>
</insert>
</mapper>
SetmealDishMapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.SetmealDishMapper">
<select id="getSetmealIdsDishIds" resultType="java.lang.Long">
select setmeal_id from setmeal_dish where dish_id in
//# collection="dishIds":Mapper类的形参名字 item="dishId":遍历后出数据的别名 要和 #{dishId}一致 就是给where dish_id(这个取的别名) separator=",":遍历一次的分隔符 open="(" :开始前 close=")":结束后
<foreach collection="dishIds" item="dishId" open="(" separator="," close=")">
#{dishId}
</foreach>
</select>
</mapper>
优化:
//删除菜品后,关联的口味数据也需要删除掉
// 批量删除菜品
dishMapper.deleteBatch(ids);
// 批量删除口味
dishFlavorMapper.deleteBatchDishFlavor(ids);
------------------------------------------------------------------
<delete id="deleteBatch" >
delete from dish where id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
--------------------------------------------------------------------
<delete id="deleteBatchDishFlavor">
delete from dish_flavor where dish_id in
<foreach collection="dishIds" item="dishId" separator="," open="(" close=")">
#{dishId}
</foreach>
</delete>
}
修改菜品
Controller层
先回显数据
@GetMapping("/{id}")
@ApiOperation("根据id查询菜品")
public Result<DishVO> getById(@PathVariable Long id){
log.info("根据id查询菜品:{}",id);
DishVO dishVO = dishService.getByIdWithFlavor(id);
return Result.success(dishVO);
}
@PutMapping()
@ApiOperation("菜品修改功能")
public Result update(@RequestBody DishDTO dishDTO){
dishService.update(dishDTO);
return Result.success();
}
Service接口
/**
* 根据id查询菜品和对应的口味数据
* @param id
* @return
*/
DishVO getByIdWithFlavor(Long id);
/**
* 修改菜品
* @param dishDTO
*/
void update(DishDTO dishDTO);
Service层
/**
* 根据id查询菜品和对应的口味数据
*
* @param id
* @return
*/
@Override
public DishVO getByIdWithFlavor(Long id) {
// 根据id查询菜品数据
Dish dish = dishMapper.getById(id);
// 根据菜品id查询口味数据
List<DishFlavor> dishFlavors = dishFlavorMapper.getflavordishId(id);
// 将查询到的数据封装到VO
DishVO dishVO = new DishVO();
BeanUtils.copyProperties(dish, dishVO);
dishVO.setFlavors(dishFlavors);
return dishVO;
}
/**
* 修改菜品
* @param dishDTO
*/
@Override
public void update(DishDTO dishDTO) {
// 修改菜品数据
Dish dish = new Dish();
BeanUtils.copyProperties(dishDTO, dish);
dishMapper.update(dish);
// 修改口味数据
// 删除口味数据
dishFlavorMapper.deleteByDishid(dishDTO.getId());
// 添加口味数据
List<DishFlavor> flavors = dishDTO.getFlavors();
if (flavors != null && flavors.size() > 0) {
flavors.forEach(dishFlavor -> {
dishFlavor.setDishId(dishDTO.getId());
System.out.println(flavors);
});
dishFlavorMapper.insertBatch(flavors);
}
Mapper层:
DishFlavorMapper
/**
* 根据菜品id查询对应的口味数据
* @param dishId
* @return
*/
@Select("select * from dish_flavor where dish_id =#{dishId}")
List<DishFlavor> getflavordishId(Long dishId);
/**
* 根据菜品id数据删除口味数据
* @param id
*/
@Delete("delete from dish_flavor where dish_id=#{dishId}")
void deleteByDishid(Long id);
/**
* 根据id查询
* @param id
* @return
*/
@Select("select * from dish where id=#{id} ")
Dish getById(Long id);
/**
* 根据id批量删除
* @param ids
*/
void deleteBatch(List<Long> ids);
/**
* 修改菜品数据
* @param dish
*/
@AutoFill(value = OperationType.UPDATE)
void update(Dish dish);
Mapper.xml:
DishFlavorMapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.DishFlavorMapper">
<insert id="insertBatch">
insert into dish_flavor (dish_id, name, value) VALUES
<foreach collection="flavors" item="df" separator=",">
(#{df.dishId},#{df.name},#{df.value})
</foreach>
</insert>
<delete id="deleteBatchDishFlavor">
delete from dish_flavor where dish_id in
<foreach collection="dishIds" item="dishId" separator="," open="(" close=")">
#{dishId}
</foreach>
</delete>
</mapper>
DishMapper
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.DishMapper">
<!-- useGeneratedKeys="true":需要主键值返回 keyProperty="id":返回主键值id -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
insert into dish (name, category_id, price, image, description, status, create_time, update_time, create_user, update_user)
VALUES
(#{name}, #{categoryId}, #{price}, #{image}, #{description}, #{status}, #{createTime}, #{updateTime}, #{createUser},
#{updateUser})
</insert>
<select id="pageQuery" resultType="com.sky.vo.DishVO">
select d.* , c.name as categoryName from dish d left outer join category c on d.category_id = c.id
<where>
<if test="name != null">
and d.name like concat('%',#{name},'%')
</if>
<if test="categoryId != null">
and d.category_id = #{categoryId}
</if>
<if test="status != null">
and d.status = #{status}
</if>
</where>
order by d.create_time desc
</select>
<delete id="deleteBatch" >
delete from dish where id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<update id="update">
update dish
<set>
<if test="name != null">name = #{name},</if>
<if test="categoryId != null">category_id = #{categoryId},</if>
<if test="price != null">price = #{price},</if>
<if test="image != null">image = #{image},</if>
<if test="description != null">description = #{description},</if>
<if test="status != null">status = #{status},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="updateUser != null">update_user = #{updateUser},</if>
</set>
where id = #{id}
</update>
Redis数据库
连接redis
数据类型介绍
字符串操作命令
哈希操作命令
列表操作命令
补充: ltrim mylist 0 6 (表示列表保留0-6范围的数据)用于多删除
集合操作命令
有序集合操作命令
java中操作Redis
数据源yml
spring:
datasource:
redis:
host: ${sky.redis.host}
port: ${sky.redis.port}
password: ${sky.datasource.password}
database: ${sky.datasource.database}
dev.yml----------------------------------------------
sky:
redis:
host: localhost
port: 6379
password: 123456
database: 10
配置类
@Configuration
@Slf4j
public class RedisConfiguration {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
log.info("开始创建redis模板对象...");
RedisTemplate redisTemplate = new RedisTemplate();
//设置redis的连接工厂对象
redisTemplate.setConnectionFactory(redisConnectionFactory);
//设置redis key的序列化器
redisTemplate.setKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
####java实现操作
/**
* 功能描述
*
* @author ayyy
* @date 2024/03/21 17:12
*/
@SpringBootTest
public class SpringDataRedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testRedisTemplate(){
System.out.println(redisTemplate);
// 字符串类型
ValueOperations valueOperations = redisTemplate.opsForValue();
// 哈希类型
HashOperations hashOperations = redisTemplate.opsForHash();
// 列表类型
ListOperations listOperations = redisTemplate.opsForList();
// 集合类型
SetOperations setOperations = redisTemplate.opsForSet();
// 有序集合类型
ZSetOperations zSetOperations = redisTemplate.opsForZSet();
}
/**
* 操作字符串类型数据
*/
@Test
public void testString(){
// set get setex setnx
// 添加
redisTemplate.opsForValue().set("city","北京");
// 获取key的 value
String city = (String) redisTemplate.opsForValue().get("city");
System.out.println(city);
redisTemplate.opsForValue().set("code","1234",3, TimeUnit.MINUTES);
redisTemplate.opsForValue().setIfAbsent("lock","1");
redisTemplate.opsForValue().setIfAbsent("lock","2");
}
/**
* 操作哈希类型数据
*/
@Test
public void testHash(){
//hset hget hdel hkey hvals
HashOperations hashOperations = redisTemplate.opsForHash();
// 添加
hashOperations.put("100","name","tom");
hashOperations.put("100","age","20");
Object name = hashOperations.get("100", "name");
System.out.println(name);
// 获取所有的键key(字段 )
Set keys = hashOperations.keys("100");
System.out.println(keys);
// 获取所有值
List values = hashOperations.values("100");
System.out.println(values);
// 删除
hashOperations.delete("100","age");
}
店铺营业状态设置
设置营业状态就只用Controller传一个值就好
Controller层
/**
* adimin 店铺相关接口
*
* @author ayyy
* @date 2024/03/21 22:57
*/
@RestController("adminShopController")
@RequestMapping("/admin/shop")
@Slf4j
@Api(tags = "店铺相关接口")
public class ShopController {
public static final String KEY = "SHOP_STATUS";
@Autowired
private RedisTemplate redisTemplate;
/**
* 设置店铺营业状态
* @param status
* @return
*/
@PutMapping("{status}")
@ApiOperation("设置店铺营业状态")
public Result setStatus(@PathVariable Integer status){
log.info("设置店铺的营业状态为:{}",status ==1?"营业中":"未营业");
redisTemplate.opsForValue().set(KEY,status);
return Result.success();
}
/**
* 获取店铺营业状态
* @return
*/
@GetMapping("/status")
@ApiOperation("获取店铺营业状态")
public Result<Integer> getStatus(){
Integer status = (Integer) redisTemplate.opsForValue().get(KEY);
log.info("获取到店铺营业状态为:{}",status==1?"营业中":"未营业");
return Result.success(status);
}
}
/**
* user店铺相关接口
*
* @author ayyy
* @date 2024/03/21 22:57
*/
@RestController("userShopController")
@RequestMapping("/user/shop")
@Slf4j
@Api(tags = "店铺相关接口")
public class ShopController {
public static final String KEY = "SHOP_STATUS";
@Autowired
private RedisTemplate redisTemplate;
/**
* 获取店铺营业状态
* @return
*/
@GetMapping("/status")
@ApiOperation("获取店铺营业状态")
public Result<Integer> getStatus(){
Integer status = (Integer) redisTemplate.opsForValue().get(KEY);
log.info("获取到店铺营业状态为:{}",status==1?"营业中":"未营业");
return Result.success(status);
}
}
微信登录
HttpClient
导包
微信小程序
不勾请求发不了
入门案例
<!--index.wxml-->
<navigation-bar title="Weixin" back="{{false}}" color="black" background="#FFF"></navigation-bar>
<scroll-view class="scrollarea" scroll-y type="list">
<view class="container">
<view>
{{msg}}
</view>
<view>
<!-- bind:tap=绑定事件 "getUserInfo":方法名称-->
<button bind:tap="getUserInfo" type="primary">获取用户信息</button>
<!-- 动态获取nickName 传入js中 -->
昵称:{{nickName}}
<image style="width: 100px; height: 100px;" src="{{url}}">
</image>
</view>
<view>
<button bind:tap="wxLogin" type="warn">微信登录</button>
</view>
授权码:{{code}}
</view>
<view>
<button bind:tap="sendRequest" type="default">发送请求</button>
</view>
</scroll-view>
// index.js
Page({
data:{
msg :'hello world',
nickName:"",
url:"",
code:""
},
// 获取微信用户头像和昵称
getUserInfo(){
wx.getUserProfile({
desc: '获取微信用户信息',
success: (res) =>{
console.log(res.userInfo);
// 为数据赋值
this.setData({
nickName: res.userInfo.nickName,
url: res.userInfo.avatarUrl
})
}
})
},
// 微信登录,获取微信用户的授权码
wxLogin(){
wx.login({
success: (res) => {
console.log(res.code);
this.setData({
code:res.code
})
},
})
},
sendRequest(){
wx.request({
url: 'http://localhost:8080/user/shop/status',
method:"GET",
success:(res)=>{
// res.data后端返回的全部数据(result的数据)
console.log(res.data)
}
})
}
})
微信登录
首先微信小程序 通过wx.login()方法获取code 然后通过wx.request()发送code给后端 后端通过 设置yml 发送凭证校验接口(就是传地址)然后微信平台会返回openid等 然后我们自己转成json来掉openid的键 获取他的值。
application.yml
sky:
jwt:
user-secret-key: ayyy
user-ttl: 720000000
user-token-name: authentication
JwtProperties
/**
* 用户端微信用户生成jwt令牌相关配置
*/
private String userSecretKey;
private long userTtl;
private String userTokenName;
Controller层
package com.sky.controller.user;
import com.sky.constant.JwtClaimsConstant;
import com.sky.dto.UserLoginDTO;
import com.sky.entity.User;
import com.sky.properties.JwtProperties;
import com.sky.result.Result;
import com.sky.service.UserService;
import com.sky.utils.JwtUtil;
import com.sky.vo.UserLoginVO;
import io.jsonwebtoken.Claims;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
/**
* 功能描述
*
* @author ayyy
* @date 2024/03/24 16:32
*/
@RestController
@RequestMapping("/user/user")
@Api("C端用户相关接口")
@Slf4j
public class UserController {
@Autowired
private UserService userService;
@Autowired
private JwtProperties jwtProperties;
/**
* 微信登录
* @param userLoginDTO
* @return
*/
@PostMapping("/login")
@ApiOperation("微信登录")
public Result<UserLoginVO> login(@RequestBody UserLoginDTO userLoginDTO){
log.info("微信登录:{}",userLoginDTO.getCode());
// 微信登录
User user = userService.wxLogin(userLoginDTO);
// 为微信用户生成jwt令牌
HashMap<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.USER_ID,user.getId());
String token = JwtUtil.createJWT(jwtProperties.getUserSecretKey(), jwtProperties.getUserTtl(), claims);
UserLoginVO userLoginVO = UserLoginVO.builder()
.id(user.getId())
.openid(user.getOpenid())
.token(token)
.build();
return Result.success(userLoginVO);
}
}
Service接口
/**
* 功能描述
*
* @author ayyy
* @date 2024/03/24 16:37
*/
public interface UserService {
User wxLogin(UserLoginDTO userLoginDTO);
}
Service层
package com.sky.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sky.constant.MessageConstant;
import com.sky.dto.UserLoginDTO;
import com.sky.entity.User;
import com.sky.exception.LoginFailedException;
import com.sky.mapper.UserMapper;
import com.sky.properties.WeChatProperties;
import com.sky.service.UserService;
import com.sky.utils.HttpClientUtil;
import io.netty.handler.codec.MessageAggregationException;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.HttpClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.HashMap;
/**
*
*
* @author ayyy
* @date 2024/03/24 17:03
*/
@Service
@Slf4j
public class UserServiceImpl implements UserService {
@Autowired
private WeChatProperties weChatProperties;
@Autowired
private UserMapper userMapper;
public static final String WX_LOGIN="https://api.weixin.qq.com/sns/jscode2session";
/**
* 微信登录
* @param userLoginDTO
* @return
*/
@Override
public User wxLogin(UserLoginDTO userLoginDTO) {
String openid = getOpenid(userLoginDTO.getCode());
//判断当前用户是否是新用户
if (openid.isEmpty()){
throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
}
User user = userMapper.getByOpenid(openid);
//如果是新用户,自动完成注册
if (user ==null){
user = User.builder()
.openid(openid)
.createTime(LocalDateTime.now())
.build();
userMapper.insert(user);
}
//返回这个用户对象
return user;
}
/**
* 调用微信接口服务,获取微信用户的openid
* @param code
* @return
*/
private String getOpenid(String code){
HashMap<String, String> map = new HashMap<>();
map.put("appid",weChatProperties.getAppid());
map.put("secret",weChatProperties.getSecret());
map.put("js_code",code);
map.put("grant_type","authorization_code");
//调用微信接口服务,获得当前微信用户的openid
String json = HttpClientUtil.doGet(WX_LOGIN, map);
//判断openid是否为空,如果为空登录失败,抛出异常
JSONObject jsonObject = JSON.parseObject(json);
// 获取key:"openid"键的 值value
String openid = jsonObject.getString("openid");
return openid;
}
}
Mapper层:
package com.sky.mapper;
import com.sky.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
/**
* 功能描述
*
* @author ayyy
* @date 2024/03/24 17:32
*/
@Mapper
public interface UserMapper {
/**
* 根据openid查询用户
* @param openid
* @return
*/
@Select("select * from user where openid=#{openid}")
User getByOpenid(String openid);
/**
* 插入数据
* @param user
*/
void insert(User user);
}
Mapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.UserMapper">
<insert id="insert" useGeneratedKeys="true" keyProperty="id" >
insert into user(openid, name, phone, sex, id_number, avatar, create_time)
values (#{openid}, #{name}, #{phone}, #{sex}, #{idNumber}, #{avatar}, #{createTime})
</insert>
</mapper>
Usertoken校验
package com.sky.interceptor;
import com.sky.constant.JwtClaimsConstant;
import com.sky.context.BaseContext;
import com.sky.properties.JwtProperties;
import com.sky.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* jwt令牌校验的拦截器
*/
@Component
@Slf4j
public class JwtTokenUserInterceptor implements HandlerInterceptor {
@Autowired
private JwtProperties jwtProperties;
/**
* 校验jwt
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断当前拦截到的是Controller的方法还是其他资源
if (!(handler instanceof HandlerMethod)) {
//当前拦截到的不是动态方法,直接放行
return true;
}
//1、从请求头中获取令牌
String token = request.getHeader(jwtProperties.getUserTokenName());
//2、校验令牌
try {
log.info("jwt校验:{}", token);
Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token);
// 解析jwt里面自带的id
Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());
log.info("当前用户id:", userId);
BaseContext.setCurrentId(userId);
//3、通过,放行
return true;
} catch (Exception ex) {
//4、不通过,响应401状态码
response.setStatus(401);
return false;
}
}
}
拦截路径
package com.sky.config;
import com.sky.interceptor.JwtTokenAdminInterceptor;
import com.sky.interceptor.JwtTokenUserInterceptor;
import com.sky.json.JacksonObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import java.util.List;
/**
* 配置类,注册web层相关组件
*/
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
@Autowired
private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;
@Autowired
private JwtTokenUserInterceptor jwtTokenUserInterceptor;
/**
* 注册自定义拦截器
*
* @param registry
*/
@Override
protected void addInterceptors(InterceptorRegistry registry) {
log.info("开始注册自定义拦截器...");
registry.addInterceptor(jwtTokenAdminInterceptor)
.addPathPatterns("/admin/**")
.excludePathPatterns("/admin/employee/login");
// 添加拦截器
registry.addInterceptor(jwtTokenUserInterceptor)
// 拦截路径
.addPathPatterns("/user/**")
// 排除拦截(无需拦截的路径)
.excludePathPatterns("/user/user/login")
.excludePathPatterns("/user/shop/status");
}
/**
* 设置静态资源映射
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
log.info("开始设置静态资源映射");
registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
/**
* 扩展Spring MVC框架的消息转化器
* @param converters
*/
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
// 创建一个消息转换器对象
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
// 需要为消息转换器设置一个对象转换器,对象转换器可以将java对象序列化为json数据
converter.setObjectMapper(new JacksonObjectMapper());
// 将自己的消息转化器加入到容器中
// 0:代表顺序排在第一位最先调用
converters.add(0,converter);
}
}
导入商品浏览功能
缓存商品数据
菜品控制层
public class DishController {
@Autowired
private DishService dishService;
@Autowired
private RedisTemplate redisTemplate;
@PostMapping()
@ApiOperation(value = "新增菜口")
public Result save(@RequestBody DishDTO dishDTO){
log.info("新增菜品:{}",dishDTO);
dishService.saveWithFlavor(dishDTO);
//清理缓存数据
String key ="dish"+ dishDTO.getCategoryId();
cleanCache(key);
return Result.success();
}
/**
* 清理缓存
* @param pattern
*/
private void cleanCache(String pattern){
Set keys = redisTemplate.keys(pattern);
redisTemplate.delete(keys);
}
缓存套餐
spring Cache
@RestController
@RequestMapping("/user")
@Slf4j
@EnableCaching
public class UserController {
@Autowired
private UserMapper userMapper;
@PostMapping
@CachePut(cacheNames = "userCache",key = "#user.id")
public User save(@RequestBody User user){
userMapper.insert(user);
return user;
}
@CacheEvict(cacheNames = "userCache",key = "#id")
@DeleteMapping
public void deleteById(Long id){
userMapper.deleteById(id);
}
// userCache下所有的键值对都删除
@CacheEvict(cacheNames = "userCache", allEntries = true)
@DeleteMapping("/delAll")
public void deleteAll(){
userMapper.deleteAll();
}
@Cacheable(cacheNames = "userCache",key = "#id")
@GetMapping
public User getById(Long id){
User user = userMapper.getById(id);
return user;
}
}
缓存套餐
购物车
添加购物车
Controller层
@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api("C端购物车相关接口")
public class ShoppingCartController {
@Autowired
private ShoppingCartService shoppingCartService;
/**
* 添加购物车
* @param shoppingCartDTO
* @return
*/
@PostMapping("/add")
@ApiOperation("添加购物车")
public Result add(@RequestBody ShoppingCartDTO shoppingCartDTO){
log.info("添加购物车,商品信息为:{}",shoppingCartDTO);
shoppingCartService.addShoppingCart(shoppingCartDTO);
return Result.success();
}
}
Service接口
/**
* 添加购物车
* @param shoppingCartDTO
*/
void addShoppingCart(ShoppingCartDTO shoppingCartDTO);
Service层
@Service
@Slf4j
public class ShoppingCartServiceImpl implements ShoppingCartService {
@Autowired
private ShoppingCartMapper shoppingCartMapper;
@Autowired
private DishMapper dishMapper;
@Autowired
private SetmealMapper setmealMapper;
/**
* 添加购物车
* @param shoppingCartDTO
*/
@Override
public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {
//判断当前加入购物车中的商品是否已经存在
ShoppingCart shoppingCart = new ShoppingCart();
BeanUtils.copyProperties(shoppingCartDTO,shoppingCart);
Long userid = BaseContext.getCurrentId();
shoppingCart.setUserId(userid);
List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);
//如果已经存在了,只需要数量加一
if (list != null && list.size()>0){
ShoppingCart cart = list.get(0);
cart.setNumber(cart.getNumber()+1);
shoppingCartMapper.updateNumberById(cart);
}else{
//如果不存在插入一条购物车数据
//判断本次添加到购物车的是菜品还是套餐
Long dishId = shoppingCartDTO.getDishId();
if (dishId != null){
//本次添加到购物车的是菜品
Dish dish = dishMapper.getById(dishId);
shoppingCart.setName(dish.getName());
shoppingCart.setImage(dish.getImage());
shoppingCart.setAmount(dish.getPrice());
}else {
//本次添加到购物车的是套餐
Long setmealId = shoppingCartDTO.getSetmealId();
Setmeal setmeal = setmealMapper.getById(setmealId);
shoppingCart.setName(setmeal.getName());
shoppingCart.setImage(setmeal.getImage());
shoppingCart.setAmount(setmeal.getPrice());
}
shoppingCart.setNumber(1);
shoppingCart.setCreateTime(LocalDateTime.now());
shoppingCartMapper.insert(shoppingCart);
}
}
}
Mapper层:
/**
* 动态条件查询
* @param shoppingCart
* @return
*/
List<ShoppingCart> list(ShoppingCart shoppingCart);
/**
* 根据id修改商品数量
* @param shoppingCart
*/
@Update("update shopping_cart set number = #{number} where id = #{id}")
void updateNumberById(ShoppingCart shoppingCart);
Mapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.ShoppingCartMapper">
<select id="list" resultType="com.sky.entity.ShoppingCart">
select * from shopping_cart
<where>
<if test="userId != null">
and user_id = #{userId}
</if>
<if test="setmealId != null">
and setmeal_id = #{setmealId}
</if>
<if test="dishId != null">
and dish_id = #{dishId}
</if>
<if test="dishFlavor != null">
and dish_flavor = #{dishFlavor}
</if>
</where>
</select>
</mapper>
查看购物车
Controller层
/**
* 查看购物车
* @return
*/
@GetMapping("/list")
@ApiOperation("看查购物车")
public Result<List<ShoppingCart>> list(){
List<ShoppingCart> list = shoppingCartService.showShoppingCart();
return Result.success(list);
}
Service接口
/**
* 查看购物车
* @return
*/
List<ShoppingCart> showShoppingCart();
Service实现层
/**
* 查看购物车
* @return
*/
@Override
public List<ShoppingCart> showShoppingCart() {
Long userId = BaseContext.getCurrentId();
ShoppingCart shoppingCart = ShoppingCart.builder()
.userId(userId)
.build();
List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);
return list;
}
清空购物车
Controller层
@DeleteMapping("/clean")
@ApiOperation("清空购物车")
public Result clean(){
shoppingCartService.cleanShoppingCart();
return Result.success();
}
}
Service接口
/**
* 清空购物车
*/
void cleanShoppingCart();
Service实现层`
@Override
public void cleanShoppingCart() {
//获取当前微信用户id
Long userId = BaseContext.getCurrentId();
ShoppingCart shoppingCart = ShoppingCart.builder()
.userId(userId)
.build();
shoppingCartMapper.deleteByUserId(userId);
}
Mapper层:
/**
* 根据用户id删除购物车数据
* @param userId
*/
@Delete("delete from shopping_cart where user_id = #{userId}")
void deleteByUserId(Long userId);
导入代码簿
用户订单
微信支付
6 会返回一个字符串
cpolar 安装(创建公网地址 IP)
Spring Task
/**
* 定时任务类,定时处理订单状态
*/
@Component
@Slf4j
public class OrderTask {
@Autowired
private OrderMapper orderMapper;
/**
* 处理超时订单的方法
*/
//每分钟触发一次
@Scheduled(cron = "0 * * * * ? ")
public void processTimeoutOrder(){
log.info("定时处理超时订单:{}", LocalDateTime.now());
LocalDateTime time = LocalDateTime.now().plusMinutes(-15);
// select * from orders where status = ? and order_time < (当前时间 - 15分钟)
List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.PENDING_PAYMENT, time);
if(ordersList != null && ordersList.size() > 0){
for (Orders orders : ordersList) {
orders.setStatus(Orders.CANCELLED);
orders.setCancelReason("订单超时,自动取消");
orders.setCancelTime(LocalDateTime.now());
orderMapper.update(orders);
}
}
}
/**
* 处理一直处于派送中状态的订单
*/
@Scheduled(cron = "0 0 1 * * ?") //每天凌晨1点触发一次
public void processDeliveryOrder(){
log.info("定时处理处于派送中的订单:{}",LocalDateTime.now());
LocalDateTime time = LocalDateTime.now().plusMinutes(-60);
List<Orders> ordersList = orderMapper.getByStatusAndOrderTimeLT(Orders.DELIVERY_IN_PROGRESS, time);
if(ordersList != null && ordersList.size() > 0){
for (Orders orders : ordersList) {
orders.setStatus(Orders.COMPLETED);
orderMapper.update(orders);
}
}
}
}
WebSocket
客户催单
Apache ECharts
营业额统计
用户统计
订单统计
销量统计排名
工作台
Apache POI
excel
前端
VUE基础
Router路由
vuex 介绍
第一个变量为函数名setname
TypeScript
前端环境搭建
分页查询
套餐分类