当前位置: 首页 > wzjs >正文

想建个网站做推广潍坊seo排名

想建个网站做推广,潍坊seo排名,网站网址正能量,渭南网站建设目录 架构的细分 使用实体类来接收配置文件中的值 webMvcConfig类: jwt令牌 管理端的拦截器: JwtProperties: JwtTokenAdminInterceptor : 对密码加密操作 Redis: 分页查询 整体思想 为什么动态 SQL 推荐传实体…

目录

架构的细分

使用实体类来接收配置文件中的值

webMvcConfig类:

jwt令牌

 管理端的拦截器:

 JwtProperties:

 JwtTokenAdminInterceptor :

对密码加密操作

Redis:

分页查询

整体思想

为什么动态 SQL 推荐传实体类?

多表操作

 Service层:

 Mapper:

动态sql——update:

删除或者增加多条数据

增加多条数据:

Service 层:

Mapper 层 

删除多条数据:

Service 层调用:

Mapper层:

总结:

什么时候需要外键约束?

 什么时候需要用到多个表的数据关联查询?

需要获取相关联表的数据: 

公共字段的处理:

微信登录后端开发:

整体的思路:

如何获取openid?

HttpClientUtil:

使用注解自动获取前端的值

1. @RequestParam

适用场景:

数据传递方式:

示例:

请求类型:

2. @RequestBody

适用场景:

数据传递方式:

示例:

请求类型:

3. @PathVariable

适用场景:

数据传递方式:

示例:

请求类型:

总结:

在更新的时候,如何进行覆盖?

如何完成两个 List 集合的转换?

转换逻辑步骤


架构的细分

首先从前单一的pojo类细分成了pojo,dto和vo,约定的就是dto接收前端的数据,vo返回给前端数据,pojo直接处理数据库的操作。(DTO用于数据传输,VO则专注于前端展示,POJO直接操作数据库)

使用实体类来接收配置文件中的值

在开发中,通常使用实体类来接收配置文件中的值,这种做法在Spring Boot等框架中较为常见。具体步骤如下:

使用实体类接收配置文件值

配置文件引用

  • application.yml 文件中,通过 ${sky.alioss.endpoint} 等形式引用了 application-dev.yml 中的 sky.alioss 配置项。这种引用方式有助于集中管理各环境的具体配置信息。
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}

配置属性绑定到实体类

  • AliOssProperties 类使用了 @ConfigurationProperties 注解,并指定了 prefix = "sky.alioss",可以直接将配置文件中的 sky.alioss 属性绑定到类中的字段。这种方式使得配置参数的管理更加结构化。
  • 使用了 @Component 注解,将 AliOssProperties 加入 Spring 容器,确保可以在项目中通过依赖注入来使用该类。
    @Component
    @ConfigurationProperties(prefix = "sky.alioss")
    @Data
    public class AliOssProperties {private String endpoint;private String accessKeyId;private String accessKeySecret;private String bucketName;
    }
    

在业务逻辑中引用配置类

  • 通过依赖注入的方式,在 JwtTokenAdminInterceptor 中使用 JwtProperties 实体类(类似于 AliOssProperties)来获取配置的 JWT 信息。
  • 例如,jwtProperties.getAdminTokenName()jwtProperties.getAdminSecretKey() 引用了 JwtProperties 中的配置项,这些配置可以通过 application.ymlapplication-dev.yml 中的值自动注入,简化了配置管理并提高了代码的可读性。

总结

  • 使用实体类接收配置文件值,通过 @ConfigurationProperties 注解实现配置属性的绑定,使得配置和代码逻辑解耦,配置更易管理。
  • 通过 ${} 方式引用其他配置文件的属性,增强了配置的灵活性,特别适合多环境配置管理。

webMvcConfig类:

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 springfox.documentation.swagger2.annotations.EnableSwagger2;import java.util.List;/*** 配置类,注册web层相关组件*/
@Configuration
@Slf4j
@EnableSwagger2
public class WebMvcConfiguration extends WebMvcConfigurationSupport {@Autowiredprivate JwtTokenAdminInterceptor jwtTokenAdminInterceptor;@Autowiredprivate JwtTokenUserInterceptor jwtTokenUserInterceptor;/*** 注册自定义拦截器** @param registry*/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");}/*** 通过knife4j生成接口文档* @return*/@Beanpublic Docket docket1() {ApiInfo apiInfo = new ApiInfoBuilder().title("苍穹外卖项目接口文档").version("2.0").description("苍穹外卖项目接口文档").build();Docket docket = new Docket(DocumentationType.SWAGGER_2).groupName("管理端接口").apiInfo(apiInfo).select().apis(RequestHandlerSelectors.basePackage("com.sky.controller.admin")).paths(PathSelectors.any()).build();return docket;}@Beanpublic Docket docket2() {ApiInfo apiInfo = new ApiInfoBuilder().title("苍穹外卖项目接口文档").version("2.0").description("苍穹外卖项目接口文档").build();Docket docket = new Docket(DocumentationType.SWAGGER_2).groupName("用户端接口").apiInfo(apiInfo).select().apis(RequestHandlerSelectors.basePackage("com.sky.controller.user")).paths(PathSelectors.any()).build();return docket;}/*** 设置静态资源映射* @param registry*/protected void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");}/*** 扩展nvc框架的消息转换器* @param converters*/protected void extendMessageConverters(List<HttpMessageConverter<?>> converters){// 创建一个消息转换器对象,把java对象转为json字符串MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();//设置对象转换器,可以将Java对象转为json字符串converter.setObjectMapper(new JacksonObjectMapper());//将我们自己的转换器放入spring Mvc框架的容器中converters.add(0,converter);}
}

webMvcConfig类里面配置的内容:

  • 拦截器配置 (addInterceptors)

    • 用于注册自定义的拦截器,拦截请求并执行特定的逻辑,比如鉴权、日志记录等。
    • addInterceptors 方法中,通过 registry.addInterceptor(ResourceHandlerRegistry registry) 将自定义拦截器加入到 Spring MVC 的拦截链中。
    • 注意!!!只有需要对所有请求都要进行身份验证的时候才需要注入到配置类,其他轻量级的就只需要Component注解。
  • Knife4j 配置

    • 配置 Swagger 接口文档生成工具 Knife4j,通常会涉及设置 Docket Bean,用于扫描控制器和接口,生成 API 文档。
  • 静态资源映射

    • 配置 Spring MVC 处理静态资源文件的方式,通常是映射 JavaScript、CSS、图片等静态文件。
  • 消息转换器配置

    • 通过扩展 Spring MVC 的 HttpMessageConverters,可以自定义消息转换器,以满足特定的 JSON 序列化、反序列化需求。

jwt令牌

登录成功后,可以使用jwtutil生成jwt令牌

JWTUtil:

package com.sky.utils;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;public class JwtUtil {/*** 生成jwt* 使用Hs256算法, 私匙使用固定秘钥** @param secretKey jwt秘钥* @param ttlMillis jwt过期时间(毫秒)* @param claims    设置的信息* @return*/public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {// 指定签名的时候使用的签名算法,也就是header那部分SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;// 生成JWT的时间long expMillis = System.currentTimeMillis() + ttlMillis;Date exp = new Date(expMillis);// 设置jwt的bodyJwtBuilder builder = Jwts.builder()// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的.setClaims(claims)// 设置签名使用的签名算法和签名使用的秘钥.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))// 设置过期时间.setExpiration(exp);return builder.compact();// 当你调用 compact() 方法时,它会将 JWT 的三部分(头部、载荷和签名)进行编码,// 并将它们合并成一个字符串。这个字符串是最终的 JWT。}/*** Token解密** @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个* @param token     加密后的token* @return*/public static Claims parseJWT(String secretKey, String token) {// 得到DefaultJwtParserClaims claims = Jwts.parser()// 设置签名的秘钥.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))// 设置需要解析的jwt.parseClaimsJws(token).getBody();return claims;}}

如何使用JWTUtil呢?首先 我们知道的是createJWT的代码需要三个参数,

jwt秘钥,jwt过期时间(毫秒),以及设置的信息(设置的信息是一个map,通常map的key是字符串(Id)Value是其id值)其实就是JWT 载荷
//登录成功后,生成jwt令牌Map<String, Object> claims = new HashMap<>();claims.put(JwtClaimsConstant.EMP_ID, employee.getId());String token = JwtUtil.createJWT(jwtProperties.getAdminSecretKey(),jwtProperties.getAdminTtl(),claims);

登录成功后,我们可以将生成的 JWT 令牌存储在另一个实体类中,并将其返回给前端。这样,在前端请求其他需要认证的接口时,可以通过在请求头中携带 JWT 令牌,拦截器就能够通过 preHandle 方法来对该令牌进行校验。通常的做法如下:

  1. 从请求中获取 JWT 令牌,通常是在请求头中,如 Authorization 字段中提取令牌。
  2. 解析 JWT 令牌,调用 JwtUtil 等工具类进行解析。
  3. 校验令牌的合法性,比如检查是否有效、是否过期、签名是否正确等。
  4. 设置用户信息,通过 BaseContext 将用户信息设置到当前线程中,以便后续的业务逻辑中使用。
  5. 拦截请求,如果令牌无效或校验失败,则返回错误信息,拒绝请求;否则,放行请求,继续执行控制器方法。

 管理端的拦截器:

同样使用使用实体类来接收配置文件中的值。这个方法在第2点提过。

 JwtProperties:

package com.sky.properties;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Component
@ConfigurationProperties(prefix = "sky.jwt")
@Data
public class JwtProperties {/*** 管理端员工生成jwt令牌相关配置*/private String adminSecretKey;private long adminTtl;private String adminTokenName;/*** 用户端微信用户生成jwt令牌相关配置*/private String userSecretKey;private long userTtl;private String userTokenName;}

 JwtTokenAdminInterceptor :

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 JwtTokenAdminInterceptor implements HandlerInterceptor {@Autowiredprivate JwtProperties jwtProperties;/*** 校验jwt** @param request* @param response* @param handler* @return* @throws Exception*/public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断当前拦截到的是Controller的方法还是其他资源if (!(handler instanceof HandlerMethod)) {//当前拦截到的不是动态方法,直接放行return true;}//1、从请求头中获取令牌String token = request.getHeader(jwtProperties.getAdminTokenName());System.out.println(token);//2、校验令牌try {log.info("jwt校验:{}", token);Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token);Long empId = Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString());log.info("当前员工id:", empId);BaseContext.setCurrentId(empId);//3、通过,放行return true;} catch (Exception ex) {//4、不通过,响应401状态码response.setStatus(401);return false;}}
}

对密码加密操作

注册的时候,需要对密码进行加密。

 employee.setPassword(DigestUtils.md5DigestAsHex(PasswordConstant.DEFAULT_PASSWORD.getBytes()));
PasswordConstant.DEFAULT_PASSWORD.是默认密码123456

Redis:

可以使用Redis来存储个别字段,例如需要被客户端和用户端同时查看到的店铺的状态。

推荐存储到Redis的情况: 

  • 用户会话信息: 使用 Redis 存储用户的登录状态、权限信息等。这些数据通常被多个客户端访问,Redis 提供了高效的读写性能。

  • 热点数据: 如果某些字段(例如商品价格、库存数量等)频繁被访问,可以将其缓存到 Redis 中,避免每次都查询数据库。

  • 共享字段: 对于需要同时由客户端和用户端访问的字段,Redis 作为缓存可以保证数据的一致性和快速访问。例如,某些公共配置或实时更新的数据,可以存放在 Redis 中,客户端和服务器都能轻松读取。

  • 缓存过期机制: Redis 提供了缓存过期的功能,当字段数据发生变化时,可以自动使缓存失效并重新加载。

分页查询

分页查询:使用pageHelper,如果存在可选的参数需要根据参数来进行分页查询的时候,特需要动态sql(如果这个参数的值不为null,那么就在sql语句添加条件限制)

 CategoryPageQueryDTO:

package com.sky.dto;import lombok.Data;import java.io.Serializable;@Data
public class CategoryPageQueryDTO implements Serializable {//页码private int page;//每页记录数private int pageSize;//分类名称private String name;//分类类型 1菜品分类  2套餐分类private Integer type;}

controller:

    /***分类分页查询* @param categoryPageQueryDTO* @return*/@GetMapping("/page")@ApiOperation("分类分页查询")public Result<PageResult> page(CategoryPageQueryDTO categoryPageQueryDTO){PageResult pageResult   =  categoryService.page(categoryPageQueryDTO);System.out.println("-----------------------------------------------");System.out.println(categoryPageQueryDTO.getType());return Result.success(pageResult);}

Service:

 /*** 分类分页查询* @param categoryPageQueryDTO* @return*/@Overridepublic PageResult page(CategoryPageQueryDTO categoryPageQueryDTO) {PageHelper.startPage(categoryPageQueryDTO.getPage(),categoryPageQueryDTO.getPageSize());Page<Category> page = categoryMapper.pageQuery(categoryPageQueryDTO);// page-> PageResultlong total = page.getTotal();List<Category> result = page.getResult();return new PageResult(total,result);}
categoryPageQueryDTO里面封装了pageSize和Page(第几个页面)

sql映射文件:

    <select id="pageQuery" resultType="com.sky.entity.Category">select * from category<where><if test="type != null"> and type = #{type} </if><if test="name != null and name != ''" >and name like concat('%',#{name},'%')</if></where>order by sort asc,create_time desc</select>

注意,第二个到后一个if标签中需要包含and字段

整体思想

对于某个单一字段的更新,我们可以先创建一个对象,然后直接调用动态的更新sql语句,参数为带有更新字段的对象,这样不需要重复写单个字段的更新的sql语句

创建对象 :

  Dish dish = Dish.builder().id(id).status(status).build();dishMapper.update(dish);

动态更新语句

<update id="update" useGeneratedKeys="true" keyProperty="id">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></set><where>id = #{id}</where></update>

我们可以在单独改变status的时候调用此接口,也可以在修改菜品的时候调用。注意在这个sql动态语句中,每一个if标签里面最后都有一个,(逗号)

但是一定要注意的是!!!!如果xml里面写的是动态sql的话,确保未使用的字段为 null 或空字符串(也就是要使用实体类,保证字段为空)

为什么动态 SQL 推荐传实体类?

动态 SQL 的逻辑依赖传入参数的字段值:

  • 动态 SQL 通过 <if> 标签判断参数是否为 null 或满足某种条件来拼接 SQL。
  • 如果只传递一个字段,而其他字段的默认值不为 null,它们仍会参与条件拼接,从而生成错误的 SQL。

我就出现过这个错误,我调用xml的动态sql,但是我的参数只有id,这个时候的动态sql:

==>  Preparing: select * from orders WHERE number like concat('%',?,'%') and phone like concat('%',?,'%') and user_id = ? and status = ? and order_time >= ? and order_time <= ? and id = ?
==> Parameters: 7(Long), 7(Long), 7(Long), 7(Long), 7(Long), 7(Long), 7(Long)
<==      Total: 0 

很明显,其他的字段都被附上了7,这个时候就错了。 

多表操作

对于涉及到多个表的操作:

就需要使用注解@Transactional

@Transactional注解用于管理事务,确保在多个数据库操作过程中要么全部成功,要么全部失败,从而保持数据的一致性和完整性。

场景描述:

  1. Table1 中插入数据后,获取生成的 ID(假设这个 ID 是自增的)。
  2. 使用这个 ID 来插入 Table2,作为外键或关联字段。

解决方法:

  1. Mapper XML 配置中,使用 useGeneratedKeyskeyProperty 来获取自增 ID。
  2. Service 层中,通过获取生成的 ID,再执行插入第二个表的操作。

具体实例如下:

新增菜品,首先我们知道菜品中有口味,口味又是一个单独的数据表,所以我们需要开启事务,保证数据的唯一性 ,同时需要注意的是,在插入了菜品后,我们需要得到对应菜品的id值来关联给Flavor_dish的dish_id值。所以我们需要用到useGeneratedKeys="true" keyProperty="id",这样在Service层中使用getId才能获得相应的属性

 Service层:

/*** 新增菜品和口味,* 涉及多张表的数据的一致性,需要加上注解-事务处理的注解** @param dishDTO*/@Override// 注意需要在启动类上面添加@EnableTransactionManagement// 开启注解方式的事务管理@Transactionalpublic void add(DishDTO dishDTO) {Dish dish = new Dish();BeanUtils.copyProperties(dishDTO, dish);// 像菜品表插入一条数据dishMapper.add(dish);// 获取insert语句生成的主键值Long id = dish.getId();List<DishFlavor> flavors = dishDTO.getFlavors();if (flavors != null && flavors.size() > 0) {// 遍历口味,赋id值flavors.forEach(dishFlavor -> {dishFlavor.setDishId(id);});// 向口味表插入n条数据disFlavorMapper.addBatch(flavors);}}

 Mapper:

    <insert id="add" useGeneratedKeys="true" keyProperty="id">insert into dish(name, category_id, price, image, description, create_time, update_time, create_user, update_user,status)values (#{name},#{categoryId},#{price},#{image},#{description},#{createTime},#{updateTime},#{createUser},#{updateUser},#{status})</insert>

动态sql——update:

<update id="update">update setmeal<set><if test="categoryId != null">category_id = #{categoryId},</if><if test="description != null">description = #{description},</if></set>where id = #{id}
</update>

注意啊!!每一个if标签文本的最后都需要逗号!!本人老是喜欢错 

删除或者增加多条数据

向一个表中增加或者删除多条数据

增加多条数据:

如果 Service 层的参数是 List 集合,并需要传递给 Mapper 层进行批量操作,可以使用 MyBatis 的批量插入或更新功能。

Service 层:
public void batchInsert(List<Entity> entities) {mapper.batchInsert(entities);
}
Mapper 层 
<insert id="batchInsert" parameterType="java.util.List">INSERT INTO 表名 (列1, 列2, 列3)VALUES<foreach collection="list" item="item" separator=",">(#{item.列1}, #{item.列2}, #{item.列3})</foreach>
</insert>

删除多条数据:

Service 层调用:
 void deleteIds(List<Long> ids);
Mapper层:

使用 IN 语法

    <delete id="deleteIds">delete from dish where id in<foreach collection="ids" item="id" open="(" close=")" separator=",">#{id}</foreach></delete>

总结:

在开发中,尽可能使用 对应类的 List 集合 作为参数,并配合 动态 SQL 来处理批量增删改查(CRUD)操作,能够显著减少 SQL 代码的重复性,提高代码的简洁性和可维护性。 其实也就是整体思想和删除或者增加多条数据的体现。

注意:我在写多条增加语句的时候,写成了:

<insert id="add">insert into setmeal_dish(setmeal_id, dish_id, name, price, copies)values<foreach collection="setmealDishes" item="ds" separator="," open="(" close=")">setmeal_id = #{ds.setmealId},dish_id=#{ds.dishId},name=#{ds.name},price = #{ds.price},copies = #{ds.copies}</foreach></insert>

 但实际上,括号要放在里面,每一次都需要括号!!!!

什么时候需要外键约束?

  • 多个表的数据关联查询:需要查询多个表中的数据。
  • 需要获取相关联表的数据:例如,查询菜品的同时也需要返回菜品所属的分类。

怎么说呢?举个例子吧:

套餐和菜品的关系
假设你有一个 setmeal_dish 表,它记录了套餐(setmeal)和菜品(dish)之间的关系。比如,一个套餐可以包含多个菜品,一个菜品也可以出现在多个套餐中。这个表通常会有两个外键:

setmeal_id(指向 setmeal 表的外键)

dish_id(指向 dish 表的外键)

  • 外键约束的作用

    • 外键约束可以确保 setmeal_dish 表中的 setmeal_iddish_id 必须分别在 setmealdish 表中存在。如果你尝试插入一个不存在的套餐ID或菜品ID,数据库会拒绝操作,从而避免了无效数据的插入。
    • 同时,外键约束还可以保证数据一致性。例如,如果删除了一个套餐(setmeal 表中的某个记录),可以通过外键约束配置,确保所有相关的套餐-菜品关系(即 setmeal_dish 表中的记录)也被删除或更新,从而避免孤立的数据。

 什么时候需要用到多个表的数据关联查询?

需要获取相关联表的数据: 

在开发中,我们需要通过套餐的id值,得到套餐里面包含的菜品 的具体信息。这个时候我们肯定需要查询这个关系表,那么我们就不能单一的写成:

<select id="getDishByCondition" resultMap="SetmealDishMap">select * from setmeal_dish<where><if test="name != null">and name = #{name}</if><if test="price != null">and price = #{price}</if><if test="setmealId != null">and setmeal_id = #{setmealId}</if></where>
</select>

因为如果这样写的话,我们只能获得菜品的id值,不能获得dish表当中具体的菜品信息。所以就应该使用多个表的关联查询:

    <select id="getDishByCondition" resultType="com.sky.vo.DishItemVO">select sd.name, sd.copies, d.image, d.descriptionfrom setmeal_dish sdleft join dish d on sd.dish_id = d.id<where><if test="name != null">and sd.name = #{name}</if><if test="price != null">and sd.price = #{price}</if><if test="setmealId != null">and sd.setmeal_id = #{setmealId}</if></where></select>

这样的话,既能获取到 setmeal_dish 的字段还能获取到对应的dish的详细信息。

总之:在查看开发文档时,注意检查响应数据是否包含了来自多个表的字段。如果响应数据中确实有多个表的字段,那么就可以考虑使用多表关联查询来获取这些数据。

公共字段的处理:

对于公共字段的处理:

需要注意的是:

AutoFillAspect 切面代码执行时,遇到 List 类型参数,会试图直接对 List 调用 setCreateTime 方法。然而,List 本身是集合接口,没有这个方法,因此会导致 NoSuchMethodException因此,在多条记录同时增加的时候我们就需要注意不能加上@AutoFill的注解

  void add(List<SetmealDish> setmealDishes);

微信登录后端开发:

微信登录的后端开发代码:

整体的思路:

  • 前端:获取code

    • 前端通过微信登录接口获取到授权码code。微信登录流程是通过微信SDK来完成的,具体步骤如下:

      • 前端调用微信的wx.login()接口,获取code
      • code代表用户的临时凭证,微信后台用它来换取用户的openidsession_key
  • 后端Controller接收到openid后调用Service层,Service调用mapper.getByOpenid(openid)
    查询数据库,判断该用户是否已注册。
  • 调用controller,controller调用Service层
  • Service层通过调用微信服务接口获取openid(使用自己封装的HttpClientUtil来发送请求获得Response响应的字符串)
    • 后端Service层接收到前端传递的code后,调用微信的API来换取openidsession_key。后端通过向微信的服务器发送请求,获取用户的openid

    • 微信的接口 URL

      https://api.weixin.qq.com/sns/jscode2session
      
      • 请求参数

        • appid: 微信小程序的appID
        • secret: 微信小程序的appSecret
        • js_code: 前端传递的code
        • grant_type: 固定为authorization_code
      • 然后调用Mapper层根据openid查询用户信息,如果用户已注册,controller层生成JWT token后封装成vo对象返回给前端;如果未注册,则返回相调用接口userMapper.add(user);。
  • 前端根据后端返回的用户信息和token来处理登录状态,进行页面跳转等操作

接下来是细节的介绍: 

主要是Service层的impl:

如何获取openid?

private String getOpenId(String code) {// 调用微信接口服务,获取当前微信用户的openidMap<String, String> map = new HashMap<>();map.put("appid", ...);  // 填入小程序的AppIDmap.put("secret", ...);  // 填入小程序的AppSecretmap.put("js_code", code); // 前端传递的codemap.put("grant_type", "authorization_code"); // 固定参数,代表授权模式// 发送HTTP请求到微信API接口获取用户信息String response = HttpClientUtil.doget("https://api.weixin.qq.com/sns/jscode2session", map);// 解析返回的JSON,获取openidJSONObject jsonObject = JSON.parseObject(response);return jsonObject.getString("openid"); // 返回openid
}

其他的ServiceImpl:

@Service
@Slf4j
public class UserServiceImpl implements UserService {// 微信服务接口地址public static final String EX_LOGIN = "https://api.weixin.qq.com/sns/jscode2session";@Autowiredprivate WeChatProperties weChatProperties;@AutowiredUserMapper userMapper;/*** 微信登录* @param loginDTO* @return*/@Overridepublic User wxlogin(UserLoginDTO loginDTO) {String openid = getOpenId(loginDTO.getCode());System.out.println(openid);// 判断openid是否为空,如果为空表示登录失败,就抛出异常if (openid == null) {throw new LoginFailedException(MessageConstant.LOGIN_FAILED);}// TODo 这里只能获取到openid,由于个人开发的权限问题其他的获取不到// 当前用户的openid是否存在于数据库(为新用户)User user =  userMapper.getByOpenid(openid);if(user == null ){user = User.builder().openid(openid).createTime(LocalDateTime.now()).build();System.out.println("==========================");System.out.println(user);// 如果是新用户,自动完成注册userMapper.add(user);}// 返回用户对象return  user;}

一个小细节:

由于返回的值需要用户的id值,所以在增加用户的mapper是xml文件形式,useGeneratedKeys="true"keyProperty="id",可以让数据库生成的自增id自动填充到User对象的id字段中,这样返回的时候user才能有id值

由于小程序端也需要保证用户是登录的状态,所以我们也需要设置一个拦截器,根据用户端的拦截器改一改方法中的一些参数的值就可以了。

同样的在webMvcconfig配置类里面注入该拦截器,将该拦截器加入到拦截链条里面即可。

HttpClientUtil:

package com.sky.utils;import com.alibaba.fastjson.JSONObject;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** Http工具类*/
public class HttpClientUtil {static final  int TIMEOUT_MSEC = 5 * 1000;/*** 发送GET方式请求* @param url* @param paramMap* @return*/public static String doGet(String url,Map<String,String> paramMap){// 创建Httpclient对象CloseableHttpClient httpClient = HttpClients.createDefault();String result = "";CloseableHttpResponse response = null;try{URIBuilder builder = new URIBuilder(url);if(paramMap != null){for (String key : paramMap.keySet()) {builder.addParameter(key,paramMap.get(key));}}URI uri = builder.build();//创建GET请求HttpGet httpGet = new HttpGet(uri);//发送请求response = httpClient.execute(httpGet);//判断响应状态if(response.getStatusLine().getStatusCode() == 200){result = EntityUtils.toString(response.getEntity(),"UTF-8");}}catch (Exception e){e.printStackTrace();}finally {try {response.close();httpClient.close();} catch (IOException e) {e.printStackTrace();}}return result;}/*** 发送POST方式请求* @param url* @param paramMap* @return* @throws IOException*/public static String doPost(String url, Map<String, String> paramMap) throws IOException {// 创建Httpclient对象CloseableHttpClient httpClient = HttpClients.createDefault();CloseableHttpResponse response = null;String resultString = "";try {// 创建Http Post请求HttpPost httpPost = new HttpPost(url);// 创建参数列表if (paramMap != null) {List<NameValuePair> paramList = new ArrayList();for (Map.Entry<String, String> param : paramMap.entrySet()) {paramList.add(new BasicNameValuePair(param.getKey(), param.getValue()));}// 模拟表单UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramList);httpPost.setEntity(entity);}httpPost.setConfig(builderRequestConfig());// 执行http请求response = httpClient.execute(httpPost);resultString = EntityUtils.toString(response.getEntity(), "UTF-8");} catch (Exception e) {throw e;} finally {try {response.close();} catch (IOException e) {e.printStackTrace();}}return resultString;}/*** 发送POST方式请求* @param url* @param paramMap* @return* @throws IOException*/public static String doPost4Json(String url, Map<String, String> paramMap) throws IOException {// 创建Httpclient对象CloseableHttpClient httpClient = HttpClients.createDefault();CloseableHttpResponse response = null;String resultString = "";try {// 创建Http Post请求HttpPost httpPost = new HttpPost(url);if (paramMap != null) {//构造json格式数据JSONObject jsonObject = new JSONObject();for (Map.Entry<String, String> param : paramMap.entrySet()) {jsonObject.put(param.getKey(),param.getValue());}StringEntity entity = new StringEntity(jsonObject.toString(),"utf-8");//设置请求编码entity.setContentEncoding("utf-8");//设置数据类型entity.setContentType("application/json");httpPost.setEntity(entity);}httpPost.setConfig(builderRequestConfig());// 执行http请求response = httpClient.execute(httpPost);resultString = EntityUtils.toString(response.getEntity(), "UTF-8");} catch (Exception e) {throw e;} finally {try {response.close();} catch (IOException e) {e.printStackTrace();}}return resultString;}private static RequestConfig builderRequestConfig() {return RequestConfig.custom().setConnectTimeout(TIMEOUT_MSEC).setConnectionRequestTimeout(TIMEOUT_MSEC).setSocketTimeout(TIMEOUT_MSEC).build();}}

使用注解自动获取前端的值

1. @RequestParam

@RequestParam 注解用于从 查询参数 中获取数据,通常用于 GET 请求 或带有查询参数的 POST 请求。前端发送的数据以 URL 的查询字符串形式传递。

适用场景:
  • 用于接收 URL 中的查询参数(query parameters)。
  • 常见于表单提交或 URL 中的查询字符串。
数据传递方式:
  • 前端通过 查询字符串 发送数据,形式如 ?name=value&age=30
示例:

前端请求:

GET /user?name=JohnDoe&age=30

如果请求中的参数是一个普通类型(例如 LongIntegerString 等),并且该参数名称与方法中的参数名称相同,则通常可以通过 @RequestParam 来自动映射。如果没有显式地指定 @RequestParam,Spring 会默认从请求参数中提取与方法参数同名的值。(因此,只要参数名和方法中的参数名称相同,那么就不需要显示写注解了)

但是,如果请求中的参数是一个集合(例如 List),那么你需要使用 @RequestParam 来指定参数名,因为 Spring 无法根据名称自动推断列表类型。

    /*** 菜品的批量删除* @param ids* @return*/@DeleteMapping@ApiOperation("菜品的批量删除")public Result delete(@RequestParam List<Long> ids){log.info("ids的值为:{}",ids);dishService.delete(ids);return Result.success();}
请求类型:
  • 主要用于 query string 类型的数据。

2. @RequestBody

@RequestBody 注解用于从 请求体(body)中获取数据,通常与 POSTPUTPATCH 请求一起使用。它常用于接收 JSONXML 数据。

适用场景:
  • 用于处理 请求体 中的复杂数据,尤其是 JSON 数据。
  • 适合用于 API 接口中,接收客户端发送的对象。
数据传递方式:
  • 前端通过 HTTP 请求的 请求体 发送数据,通常是 JSON 格式。
示例:

前端请求:

POST /user
Content-Type: application/json{"name": "JohnDoe","age": 30
}

注意:

  • @RequestBody 注解通常与 对象类型 参数结合使用,Spring 会自动将 JSON 转换成相应的 Java 对象。
  • 需要确保请求头的 Content-Typeapplication/json 或其他合适的媒体类型。
请求类型:
  • 适用于 JSONXML 格式的数据,通常是 POST 或 PUT 请求。

3. @PathVariable

@PathVariable 注解用于从 路径参数 中获取数据,通常用于 RESTful 风格的 URL 中的动态部分。这些动态值是 URL 中的占位符。

适用场景:
  • 用于从 URL 中的路径中获取参数,常用于 REST API 的路径设计。
  • 适合处理资源的唯一标识符,如 ID。
数据传递方式:
  • 前端通过 URL 的路径 传递参数,通常用于资源标识符,如用户 ID、商品 ID 等。
示例:

前端请求:

GET /user/123

注意:

  • @PathVariable 注解用于 URL 模板中的路径变量部分。
  • 路径参数和方法参数之间的映射是通过变量名称来自动匹配的。
请求类型:
  • 用于 路径参数 的数据,通常是 RESTful URL 中的一部分。

总结:

  1. @RequestParam

    • 用于获取 查询参数(query parameters)。
    • 常见于 GET 请求,数据以查询字符串形式传递。
  2. @RequestBody

    • 用于获取 请求体 中的数据,通常是 JSON 或 XML。
    • 常见于 POSTPUT 请求。
  3. @PathVariable

    • 用于获取 路径参数,通常是 URL 的动态部分(如 ID)。
    • 常见于 RESTful API。

在更新的时候,如何进行覆盖?

就比如说苍穹外卖吧,比如说在修改菜品的时候还需要修改口味,这个时候我刚开始的时候想的是修改菜品+修改口味,我有一个想法就是通过菜品的id值,查数据库找到口味的id值(list集合),然后进行修改。但其实还有一个更加简便的方法就是直接先根据菜品id直接删除最后在添加。

其实这两种没有本质区别,知识第二种逻辑更加简单罢了。

如何完成两个 List 集合的转换?

转换逻辑步骤

  1. 使用 stream() 转换流

    • orderDetailList.stream() 将订单详情列表 orderDetailList 转换为流,便于对其中的每个元素进行操作。
  2. 使用 map() 映射操作

    • map 是流操作的一部分,作用是将流中的每个元素进行映射转换。
    • 通过映射操作,将 OrderDetail 对象转换为 ShoppingCart 对象。
  3. 复制属性

    • BeanUtils.copyProperties(x, shoppingCart, "id") 复制两个对象之间的相同属性,忽略 id
    • 忽略字段 "id" 是因为你可能希望使用其他逻辑生成新的 id,或者保留购物车对象的 id 不变。
  4. 添加其他属性

    • 设置 userIdcreateTime 等特定的属性,这些属性在订单详情中可能不存在,需要手动赋值。
  5. 收集结果

    • collect(Collectors.toList()) 将流操作的结果收集为一个新的 List 集合。
	/*** 再来一单** @param id*/public void repetition(Long id) {// 查询当前用户idLong userId = BaseContext.getCurrentId();// 根据订单id查询当前订单详情List<OrderDetail> orderDetailList = orderDetailMapper.getByOrderId(id);// 将订单详情对象转换为购物车对象List<ShoppingCart> shoppingCartList = orderDetailList.stream().map(x -> {ShoppingCart shoppingCart = new ShoppingCart();// 将原订单详情里面的菜品信息重新复制到购物车对象中BeanUtils.copyProperties(x, shoppingCart, "id");shoppingCart.setUserId(userId);shoppingCart.setCreateTime(LocalDateTime.now());return shoppingCart;}).collect(Collectors.toList());// 将购物车对象批量添加到数据库shoppingCartMapper.insertBatch(shoppingCartList);}

http://www.dtcms.com/wzjs/201138.html

相关文章:

  • 建设网站源码太原seo排名外包
  • 网站开发如何避免浏览器缓存的影响seo顾问服
  • 某网站的安全建设方案最近新闻今日头条
  • app地推网seo 网站优化推广排名教程
  • 大型大型网站建设方案ppt西安seo网站管理
  • 如何做动漫网站优秀网站网页设计
  • 服务好的南昌网站设计学大教育一对一收费价格表
  • 大连模板网站制作公司电话重庆做优化的网络公司
  • 100款免费软件网站大全roseonly企业网站优化
  • 网上做调查网站重庆seo网络推广优化
  • 重庆网站建设吧seo招聘信息
  • 成都网站建设 公司深圳网络络推广培训
  • 做外贸自己做网站么一句话宣传自己的产品
  • 域名服务网站怎么注册域名网址
  • 内部网站建设教程知名网页设计公司
  • 网站手机端优化百度首页的ip地址
  • 网站构成要素外贸b2b平台都有哪些网站
  • gif网站banner怎么做互联网营销专业
  • 园州网站建设引流推广网站平台
  • 找出网站所有死链接如何让网站快速收录
  • 1号店网站模板下载潍坊seo网络推广
  • 门户类网站如何做策划论坛推广工具
  • 徐州手机网站营销公司哪家好安卓手机优化神器
  • 广州域名注册郑州seo优化培训
  • 游戏网站建设的策划百度提交网站入口网址
  • 网站建设合肥手机百度官网
  • wordpress新主题tint优化科技
  • 虚拟空间做网站武汉网站竞价推广
  • 大同百度做网站多少钱网络营销是什么专业
  • 个人做排行网站网络推广是干嘛的