feat: 网页非鉴权接口开发

This commit is contained in:
张云杰 2025-08-31 21:00:53 +08:00
parent 119f66a833
commit 15c6df8a03
16 changed files with 576 additions and 0 deletions

View File

@ -0,0 +1,28 @@
package com.byhah.cloud.admin.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.byhah.cloud.admin.module.anon.dto.WebsiteCaseSearchDto;
import com.byhah.cloud.admin.module.anon.dto.WebsiteCommentSearchDto;
import com.byhah.cloud.admin.module.anon.vo.WebsiteCaseVo;
import com.byhah.cloud.admin.module.anon.vo.WebsiteCommentVo;
import com.byhah.cloud.basic.core.entity.website.WebsiteComment;
import org.apache.ibatis.annotations.Param;
/**
* 网站非鉴权mapper
*
* @author ZYJ
*/
public interface AnonMapper {
/**
* 成功案例分页
*/
IPage<WebsiteCaseVo> casePage(Page<?> page, @Param("dto") WebsiteCaseSearchDto dto);
/**
* 客户评价分页
*/
IPage<WebsiteCommentVo> commentPage(Page<?> page, @Param("dto") WebsiteCommentSearchDto dto);
}

View File

@ -0,0 +1,59 @@
package com.byhah.cloud.admin.module.anon.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.byhah.cloud.admin.module.anon.dto.WebsiteCaseSearchDto;
import com.byhah.cloud.admin.module.anon.dto.WebsiteCommentSearchDto;
import com.byhah.cloud.admin.module.anon.dto.WebsiteConsultSaveDto;
import com.byhah.cloud.admin.module.anon.service.WebsiteService;
import com.byhah.cloud.admin.module.anon.vo.WebsiteCaseVo;
import com.byhah.cloud.admin.module.anon.vo.WebsiteCommentVo;
import com.byhah.cloud.admin.module.anon.vo.WebsiteVo;
import com.byhah.cloud.admin.module.website.dto.WebsiteTargetDto;
import com.byhah.deploy.basic.util.web.PageUtils;
import com.byhah.deploy.basic.validation.group.Insert;
import com.byhah.deploy.starter.web.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* 对外暴露的网站接口
*
* @author ZYJ
*/
@Tag(name = "对外暴露的网站接口")
@RestController
@RequiredArgsConstructor
@RequestMapping("/anon/website")
public class AnonWebsiteController {
private final WebsiteService websiteService;
@Operation(summary = "获取网站信息")
@GetMapping("/info")
R<WebsiteVo> info() {
return R.ok(websiteService.info());
}
@Operation(summary = "获取成功案例分页")
@GetMapping("/casePage")
R<IPage<WebsiteCaseVo>> casePage(WebsiteCaseSearchDto dto) {
return R.ok(websiteService.casePage(PageUtils.toPage(), dto));
}
@Operation(summary = "获取客户评价分页")
@GetMapping("/commentPage")
R<IPage<WebsiteCommentVo>> commentPage(WebsiteCommentSearchDto dto) {
return R.ok(websiteService.commentPage(PageUtils.toPage(), dto));
}
@Operation(summary = "保存咨询信息")
@PostMapping("/saveConsult")
R<Void> saveConsult(@RequestBody WebsiteConsultSaveDto dto) {
websiteService.saveConsult(dto);
return R.ok();
}
}

View File

@ -0,0 +1,22 @@
package com.byhah.cloud.admin.module.anon.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 成功案例查询参数
*
* @author ZYJ
**/
@Data
public class WebsiteCaseSearchDto {
@Schema(description = "案例类型id")
private Long typeId;
@Schema(description = "当前页码")
private Integer pageNum;
@Schema(description = "每页数量")
private Integer pageSize;
}

View File

@ -0,0 +1,16 @@
package com.byhah.cloud.admin.module.anon.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 评价查询参数
*
* @author ZYJ
**/
@Data
public class WebsiteCommentSearchDto {
@Schema(description = "每页数量")
private Integer pageSize;
}

View File

@ -0,0 +1,29 @@
package com.byhah.cloud.admin.module.anon.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 保存咨询信息参数
*
* @author ZYJ
**/
@Data
public class WebsiteConsultSaveDto {
@Schema(description = "姓名")
private String name;
@Schema(description = "电话")
private String phone;
@Schema(description = "邮箱")
private String email;
@Schema(description = "咨询类型id")
private Long typeId;
@Schema(description = "内容")
private String content;
}

View File

@ -0,0 +1,115 @@
package com.byhah.cloud.admin.module.anon.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.byhah.cloud.admin.mapper.AnonMapper;
import com.byhah.cloud.admin.module.anon.dto.WebsiteCaseSearchDto;
import com.byhah.cloud.admin.module.anon.dto.WebsiteCommentSearchDto;
import com.byhah.cloud.admin.module.anon.dto.WebsiteConsultSaveDto;
import com.byhah.cloud.admin.module.anon.vo.*;
import com.byhah.cloud.admin.module.website.service.*;
import com.byhah.cloud.basic.constant.RedisKeys;
import com.byhah.cloud.basic.core.entity.website.*;
import com.byhah.deploy.basic.core.utils.Asserts;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.Duration;
import java.util.List;
import java.util.Objects;
/**
* 网站接口Service接口实现类
*
* @author ZYJ
*/
@Service
@RequiredArgsConstructor
public class WebsiteService {
private final WebsiteTargetService targetService;
private final WebsiteProductService productService;
private final WebsiteConsultService consultService;
private final WebsiteBaseInfoService baseInfoService;
private final WebsiteConsultTypeService consultTypeService;
private final WebsiteSoftwarePlanService softwarePlanService;
private final WebsiteSuccessCaseTypeService successCaseTypeService;
private final AnonMapper anonMapper;
private final RedissonClient redissonClient;
@Transactional(rollbackFor = Exception.class, readOnly = true)
public WebsiteVo info() {
WebsiteVo vo = new WebsiteVo();
// 网站基础信息
WebsiteBaseInfo baseInfo = baseInfoService.lambdaQuery().list().stream().findFirst().orElse(new WebsiteBaseInfo());
BeanUtil.copyProperties(baseInfo, vo);
// 关于我们标签-目标信息
List<WebsiteTargetVo> targets = targetService.lambdaQuery().orderByAsc(WebsiteTarget::getSort).list().stream().map(WebsiteTargetVo::new).toList();
vo.setTargets(targets);
// 软件服务标签-解决方案
List<WebsiteSoftwarePlanVo> plans = softwarePlanService.lambdaQuery().orderByAsc(WebsiteSoftwarePlan::getSort).list().stream().map(WebsiteSoftwarePlanVo::new).toList();
// 成功案例类型
List<WebsiteSuccessCaseTypeVo> caseTypes = successCaseTypeService.lambdaQuery().orderByAsc(WebsiteSuccessCaseType::getSort).list().stream().map(WebsiteSuccessCaseTypeVo::new).toList();
vo.setCaseTypes(caseTypes);
// 咨询类型
List<WebsiteConsultTypeVo> consultTypes = consultTypeService.lambdaQuery().orderByAsc(WebsiteConsultType::getSort).list().stream().map(WebsiteConsultTypeVo::new).toList();
vo.setConsultTypes(consultTypes);
// 硬件产品
List<WebsiteProductVo> products = productService.lambdaQuery().orderByAsc(WebsiteProduct::getSort).list().stream().map(WebsiteProductVo::new).toList();
vo.setProducts(products);
return vo;
}
@Transactional(rollbackFor = Exception.class, readOnly = true)
public IPage<WebsiteCaseVo> casePage(Page<WebsiteSuccessCase> page, WebsiteCaseSearchDto dto) {
return anonMapper.casePage(page, dto);
}
@Transactional(rollbackFor = Exception.class, readOnly = true)
public IPage<WebsiteCommentVo> commentPage(Page<WebsiteComment> page, WebsiteCommentSearchDto dto) {
return anonMapper.commentPage(page, dto);
}
@Transactional(rollbackFor = Exception.class)
public void saveConsult(WebsiteConsultSaveDto dto) {
limit(dto.getPhone(), dto.getEmail());
WebsiteConsult consult = new WebsiteConsult();
BeanUtil.copyProperties(dto, consult);
consultService.save(consult);
}
/**
* 限流
*/
private void limit(String phone, String email) {
// 手机号维度限流
if (StrUtil.isNotBlank(phone)) {
// 1.1 短期限流3秒内只能发起一次支付防误触
RRateLimiter userLimiter = redissonClient.getRateLimiter(RedisKeys.RATE_LIMIT_PHONE + phone);
userLimiter.trySetRate(RateType.OVERALL, 1, Duration.ofSeconds(3), Duration.ofSeconds(10));
Asserts.isTrue(userLimiter.tryAcquire(), "重复提交3秒后再尝试");
// 1.2 中期限流1分钟内最多5次支付请求防刷单
RRateLimiter userMinuteLimiter = redissonClient.getRateLimiter(RedisKeys.RATE_LIMIT_PHONE_MINUTE + phone);
userMinuteLimiter.trySetRate(RateType.OVERALL, 5, Duration.ofMinutes(1), Duration.ofMinutes(2));
Asserts.isTrue(userMinuteLimiter.tryAcquire(), "提交过于频繁,请稍后再试");
}
// 邮箱维度限流
if (StrUtil.isNotBlank(email)) {
// 1.1 短期限流3秒内只能发起一次支付防误触
RRateLimiter userLimiter = redissonClient.getRateLimiter(RedisKeys.RATE_LIMIT_EMAIL + email);
userLimiter.trySetRate(RateType.OVERALL, 1, Duration.ofSeconds(3), Duration.ofSeconds(10));
Asserts.isTrue(userLimiter.tryAcquire(), "重复提交3秒后再尝试");
// 1.2 中期限流1分钟内最多5次支付请求防刷单
RRateLimiter userMinuteLimiter = redissonClient.getRateLimiter(RedisKeys.RATE_LIMIT_EMAIL_MINUTE + email);
userMinuteLimiter.trySetRate(RateType.OVERALL, 5, Duration.ofMinutes(1), Duration.ofMinutes(2));
Asserts.isTrue(userMinuteLimiter.tryAcquire(), "提交过于频繁,请稍后再试");
}
}
}

View File

@ -0,0 +1,28 @@
package com.byhah.cloud.admin.module.anon.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 成功案例
*
* @author ZYJ
**/
@Data
public class WebsiteCaseVo {
@Schema(description = "案例名称")
private String name;
@Schema(description = "案例图片")
private String pic;
@Schema(description = "案例描述")
private String remark;
@Schema(description = "案例类型id")
private Long typeId;
@Schema(description = "案例类型名称")
private String typeName;
}

View File

@ -0,0 +1,33 @@
package com.byhah.cloud.admin.module.anon.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 评价
*
* @author ZYJ
**/
@Data
public class WebsiteCommentVo {
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@Schema(description = "评价人名称")
private String name;
@Schema(description = "评价人头像")
private String avatar;
@Schema(description = "评价人描述")
private String remark;
@Schema(description = "评价内容")
private String content;
@Schema(description = "评价星级")
private String star;
}

View File

@ -0,0 +1,27 @@
package com.byhah.cloud.admin.module.anon.vo;
import com.byhah.cloud.basic.core.entity.website.WebsiteConsultType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 咨询类型
*
* @author ZYJ
*/
@Data
public class WebsiteConsultTypeVo {
@Schema(description = "类型id")
private Long id;
@Schema(description = "类型名称")
private String name;
public WebsiteConsultTypeVo(WebsiteConsultType consultType) {
this.id = consultType.getId();
this.name = consultType.getName();
}
}

View File

@ -0,0 +1,37 @@
package com.byhah.cloud.admin.module.anon.vo;
import com.byhah.cloud.basic.core.entity.website.WebsiteProduct;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 硬件产品
*
* @author ZYJ
*/
@Data
public class WebsiteProductVo {
@Schema(description = "产品名称")
private String name;
@Schema(description = "产品图片")
private String pic;
@Schema(description = "产品标签(左上角标识)")
private String tag;
@Schema(description = "产品描述")
private String remark;
@Schema(description = "额外信息. 产品下方的优势信息")
private WebsiteProduct.ProductExtra extra;
public WebsiteProductVo(WebsiteProduct product) {
this.name = product.getName();
this.pic = product.getPic();
this.tag = product.getTag();
this.remark = product.getRemark();
this.extra = product.getExtra();
}
}

View File

@ -0,0 +1,29 @@
package com.byhah.cloud.admin.module.anon.vo;
import com.byhah.cloud.basic.core.entity.website.WebsiteSoftwarePlan;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 软件服务解决方案
*
* @author ZYJ
*/
@Data
public class WebsiteSoftwarePlanVo {
@Schema(description = "方案名称")
private String name;
@Schema(description = "方案图片")
private String pic;
@Schema(description = "方案描述")
private String remark;
public WebsiteSoftwarePlanVo(WebsiteSoftwarePlan softwarePlan) {
this.name = softwarePlan.getName();
this.pic = softwarePlan.getPic();
this.remark = softwarePlan.getRemark();
}
}

View File

@ -0,0 +1,25 @@
package com.byhah.cloud.admin.module.anon.vo;
import com.byhah.cloud.basic.core.entity.website.WebsiteSuccessCaseType;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 成功案例类型
*
* @author ZYJ
*/
@Data
public class WebsiteSuccessCaseTypeVo {
@Schema(description = "类型id")
private Long id;
@Schema(description = "类型名称")
private String name;
public WebsiteSuccessCaseTypeVo(WebsiteSuccessCaseType caseType) {
this.id = caseType.getId();
this.name = caseType.getName();
}
}

View File

@ -0,0 +1,31 @@
package com.byhah.cloud.admin.module.anon.vo;
import com.byhah.cloud.basic.core.entity.website.WebsiteTarget;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 关于我们标签-目标信息
*
* @author ZYJ
*/
@Data
public class WebsiteTargetVo {
@Schema(description = "名称")
private String name;
@Schema(description = "图片")
private String pic;
@Schema(description = "描述")
private String remark;
public WebsiteTargetVo(WebsiteTarget websiteTarget) {
this.name = websiteTarget.getName();
this.pic = websiteTarget.getPic();
this.remark = websiteTarget.getRemark();
}
}

View File

@ -0,0 +1,49 @@
package com.byhah.cloud.admin.module.anon.vo;
import com.byhah.cloud.basic.core.entity.website.WebsiteBaseInfo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 网站信息
*
* @author ZYJ
*/
@Data
public class WebsiteVo {
@Schema(description = "主页信息. 标语、图片信息、服务")
private WebsiteBaseInfo.IndexInfo indexInfo;
@Schema(description = "硬件产品. 标语")
private WebsiteBaseInfo.ProductInfo productInfo;
@Schema(description = "软件服务. 标语")
private WebsiteBaseInfo.SoftwareInfo softwareInfo;
@Schema(description = "成功案例. 标语")
private WebsiteBaseInfo.SuccessInfo successInfo;
@Schema(description = "关于我们. 标语、数量信息")
private WebsiteBaseInfo.AboutUsInfo aboutUsInfo;
@Schema(description = "联系信息")
private WebsiteBaseInfo.ContactInfo contactInfo;
@Schema(description = "关于我们标签-目标信息")
private List<WebsiteTargetVo> targets;
@Schema(description = "软件服务标签-软件方案")
private List<WebsiteSoftwarePlanVo> plans;
@Schema(description = "成功案例类型")
private List<WebsiteSuccessCaseTypeVo> caseTypes;
@Schema(description = "咨询类型")
private List<WebsiteConsultTypeVo> consultTypes;
@Schema(description = "硬件产品")
private List<WebsiteProductVo> products;
}

View File

@ -0,0 +1,30 @@
<?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.byhah.cloud.admin.mapper.AnonMapper">
<select id="casePage" resultType="com.byhah.cloud.admin.module.anon.vo.WebsiteCaseVo">
SELECT
c.*,
ct.name AS type_name
FROM
website_success_case c
LEFT JOIN website_success_case_type ct ON C.type_id = ct.id
WHERE
c.deleted = FALSE
<if test="dto.typeId != null">
AND c.type_id = #{dto.typeId}
</if>
order by c.sort, c.created_at desc
</select>
<select id="commentPage" resultType="com.byhah.cloud.admin.module.anon.vo.WebsiteCommentVo">
SELECT
*
FROM
website_comment
WHERE
deleted = FALSE
AND enabled = TRUE
order by sort, created_at desc
</select>
</mapper>

View File

@ -1,5 +1,23 @@
package com.byhah.cloud.basic.constant;
/**
* redis对应key常量类
*
* @author ZYJ
*/
public interface RedisKeys {
// 保存咨询中的各种限流
/**
* 手机号
*/
String RATE_LIMIT_PHONE = "rate-limit-phone-";
String RATE_LIMIT_PHONE_MINUTE = "rate-limit-phone-minute-";
/**
* 邮箱
*/
String RATE_LIMIT_EMAIL = "rate-limit-email-";
String RATE_LIMIT_EMAIL_MINUTE = "rate-limit-email-minute-";
}