jwt登录验证、微信小程序登录、代码自动生成功能

wxpay
LI-CCONG\李聪聪 8 months ago
parent 90260bdc41
commit fd64c134b0

@ -30,6 +30,52 @@
<artifactId>tomcat-embed-core</artifactId>
<scope>provided</scope>
</dependency>
<!-- mysql connector -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- mybatis plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--mybatis-plus-generator 生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- json -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
<!-- hutool工具 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<!-- swagger -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
</dependency>
</dependencies>
</project>

@ -0,0 +1,45 @@
package cc.yunxi.common.domain;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import javax.validation.constraints.Min;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@Data
@ApiModel(description = "分页查询条件")
@Accessors(chain = true)
public class PageQueryPlus {
public static final Integer DEFAULT_PAGE_SIZE = 20;
public static final Integer DEFAULT_PAGE_NUM = 1;
@ApiModelProperty("页码")
@Min(value = 1, message = "页码不能小于1")
private Integer pageNo = DEFAULT_PAGE_NUM;
@ApiModelProperty("每页个数")
@Min(value = 1, message = "每页查询数量不能小于1")
private Integer pageSize = DEFAULT_PAGE_SIZE;
@ApiModelProperty("排序规则")
private Collection<SortingFieldDTO> sortingFields;
// public <T> Page<T> buildPage() {
// Page<T> page = new Page<>(pageNo, pageSize);
// if (!CollectionUtil.isEmpty(sortingFields)) {
// page.addOrder(sortingFields.stream().map(sortingField -> SortingFieldDTO.ORDER_ASC.equals(sortingField.getOrder()) ?
// OrderItem.asc(sortingField.getField()) : OrderItem.desc(sortingField.getField()))
// .collect(Collectors.toList()));
// }
// }
}

@ -0,0 +1,34 @@
package cc.yunxi.common.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SortingFieldDTO implements Serializable {
/**
* -
*/
public static final String ORDER_ASC = "asc";
/**
* -
*/
public static final String ORDER_DESC = "desc";
/**
*
*/
@ApiModelProperty("字段")
private String field;
/**
*
*/
@ApiModelProperty("是否升序")
private String order;
}

@ -0,0 +1,82 @@
package cc.yunxi.common;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
import com.baomidou.mybatisplus.generator.fill.Column;
import java.util.Collections;
public class genCodeMain {
public static void main(String[] args) {
//1、配置数据源
FastAutoGenerator.create("jdbc:mysql://222.71.165.188:3309/jnpf_ningxia", "root", "linus,.123")
//2、全局配置
.globalConfig(builder -> {
builder.author("ccongli") // 设置作者名
.outputDir(System.getProperty("user.dir") + "/src/main/java") //设置输出路径:项目的 java 目录下
.commentDate("yyyy-MM-dd hh:mm:ss") //注释日期
.dateType(DateType.ONLY_DATE) //定义生成的实体类中日期的类型 TIME_PACK=LocalDateTime;ONLY_DATE=Date;
.fileOverride() // 覆盖之前的文件
.enableSwagger() // 开启 swagger 模式
.disableOpenDir(); // 禁止打开输出目录,默认打开
})
//3、包配置
.packageConfig(builder -> {
builder.parent("cc.yunxi") // 设置父包名
.moduleName("") //设置模块包名
.entity("domain.po") //pojo 实体类包名
.service("service") //Service 包名
.serviceImpl("service.impl") // ***ServiceImpl 包名
.mapper("mapper") //Mapper 包名
.xml("mapper") //Mapper XML 包名
.controller("controller") //Controller 包名
.other("utils") //自定义文件包名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, System.getProperty("user.dir")+"/src/main/resources/mapper")); //配置 mapper.xml 路径信息:项目的 resources 目录下
})
//4、策略配置
.strategyConfig(builder -> {
builder.addInclude("nx_client") // 设置需要生成的数据表名
.addTablePrefix("nx") // 设置过滤表前缀
//4.1、Mapper策略配置
.mapperBuilder()
.superClass(BaseMapper.class) //设置父类
.formatMapperFileName("%sMapper") //格式化 mapper 文件名称
.enableMapperAnnotation() //开启 @Mapper 注解
.formatXmlFileName("%sMapper") //格式化 Xml 文件名称
//4.2、service 策略配置
.serviceBuilder()
.formatServiceFileName("%sService") //格式化 service 接口文件名称,%s进行匹配表名如 UserService
.formatServiceImplFileName("%sServiceImpl") //格式化 service 实现类文件名称,%s进行匹配表名如 UserServiceImpl
//4.3、实体类策略配置
.entityBuilder()
.enableLombok() //开启 Lombok
.disableSerialVersionUID() //不实现 Serializable 接口,不生产 SerialVersionUID
// .logicDeleteColumnName("deleted") //逻辑删除字段名
.naming(NamingStrategy.underline_to_camel) // 数据库表映射到实体的命名策略:下划线转驼峰命
.columnNaming(NamingStrategy.underline_to_camel) // 数据库表字段映射到实体的命名策略:下划线转驼峰命
.addTableFills(
new Column("create_time", FieldFill.INSERT),
new Column("modify_time", FieldFill.INSERT_UPDATE)
) //添加表字段填充,"create_time"字段自动填充为插入时间,"modify_time"字段自动填充为插入修改时间
.enableTableFieldAnnotation() // 开启生成实体时生成字段注解
//4.4、Controller策略配置
.controllerBuilder()
.formatFileName("%sController") //格式化 Controller 类文件名称,%s进行匹配表名如 UserController
.enableRestStyle(); //开启生成 @RestController 控制器
})
//5、模板
.templateEngine(new FreemarkerTemplateEngine())
// .templateEngine(new VelocityTemplateEngine())
//6、执行
.execute();
}
}

@ -29,6 +29,15 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--加密-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-rsa</artifactId>
</dependency>
</dependencies>
</project>

@ -0,0 +1,13 @@
package cc.yunxi.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
@Data
@ConfigurationProperties(prefix = "nxhs.auth")
public class AuthProperties {
private List<String> includePaths;
private List<String> excludePaths;
}

@ -0,0 +1,16 @@
package cc.yunxi.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.io.Resource;
import java.time.Duration;
@Data
@ConfigurationProperties(prefix = "nxhs.jwt")
public class JwtProperties {
private Resource location;
private String password;
private String alias;
private Duration tokenTTL = Duration.ofMinutes(10);
}

@ -0,0 +1,33 @@
package cc.yunxi.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
import java.security.KeyPair;
@Configuration
@EnableConfigurationProperties(JwtProperties.class)
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public KeyPair keyPair(JwtProperties properties){
// 获取秘钥工厂
KeyStoreKeyFactory keyStoreKeyFactory =
new KeyStoreKeyFactory(
properties.getLocation(),
properties.getPassword().toCharArray());
//读取钥匙对
return keyStoreKeyFactory.getKeyPair(
properties.getAlias(),
properties.getPassword().toCharArray());
}
}

@ -0,0 +1,76 @@
package cc.yunxi.config;
import cc.yunxi.interceptor.LoginInterceptor;
import cc.yunxi.interceptor.StatsInterceptor;
import cn.hutool.core.collection.CollUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Arrays;
import java.util.List;
@Configuration
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class WebMvcConfig implements WebMvcConfigurer {
private final LoginInterceptor loginInterceptor;
private final StatsInterceptor statsInterceptor;
private final AuthProperties authProperties;
// 全局忽略路径
private static List<String> globalExcludePaths = Arrays.asList(
"/error",
"/favicon.ico",
"/v2/**",
"/v3/**",
"/swagger-resources/**",
"/webjars/**",
"/doc.html"
);
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 所有接口
.allowCredentials(true) // 是否发送 Cookie
.allowedOriginPatterns("*") // 支持域
.allowedMethods("GET", "POST", "PUT", "DELETE") // 支持方法
.allowedHeaders("*")
.exposedHeaders("*");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 按顺序执行拦截器
this.addStatsInterceptor(registry);
this.addLoginInterceptor(registry);
}
// 添加登录拦截器
private void addLoginInterceptor(InterceptorRegistry registry) {
InterceptorRegistration registration = registry.addInterceptor(loginInterceptor);
// 2.配置拦截路径
List<String> includePaths = authProperties.getIncludePaths();
if (CollUtil.isNotEmpty(includePaths)) {
registration.addPathPatterns(includePaths);
}
// 3.配置放行路径
List<String> excludePaths = authProperties.getExcludePaths();
if (CollUtil.isNotEmpty(excludePaths)) {
registration.excludePathPatterns(excludePaths);
}
registration.excludePathPatterns(globalExcludePaths);
}
// 添加请求日志拦截器
private void addStatsInterceptor(InterceptorRegistry registry) {
registry.addInterceptor(statsInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(globalExcludePaths);
}
}

@ -0,0 +1,17 @@
package cc.yunxi.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
*
*/
@Data
@Component
@ConfigurationProperties(prefix = "nxhs.wx.recycler")
public class WxHsyProperties {
private String appId;
private String appSecret;
}

@ -0,0 +1,20 @@
package cc.yunxi.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
*
* </p>
*
* @author ccongli
* @since 2024-02-28 06:08:09
*/
@RestController
@RequestMapping("/client")
public class ClientController {
}

@ -0,0 +1,96 @@
package cc.yunxi.controller;
import cc.yunxi.common.domain.CommonResult;
import cc.yunxi.common.exception.BizIllegalException;
import cc.yunxi.config.WxHsyProperties;
import cc.yunxi.domain.dto.UserDTO;
import cc.yunxi.domain.dto.form.WxLoginDTO;
import cc.yunxi.domain.po.Recycler;
import cc.yunxi.service.IRecyclerService;
import cc.yunxi.utils.JwtTool;
import cc.yunxi.utils.WeChatUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@Api(tags = "统一接口")
@RestController
@RequestMapping("/common")
@RequiredArgsConstructor
@Slf4j
public class CommonController {
private final String code2SessionUrl = "https://api.weixin.qq.com/sns/jscode2session";
private final IRecyclerService recyclerService;
@Resource
private WxHsyProperties wxHsyProperties;
@Resource
private JwtTool jwtTool;
@ApiOperation("登录")
@PostMapping("/login")
public CommonResult<UserDTO> login(@RequestBody WxLoginDTO wxLoginDTO) {
log.info("login request body{}", wxLoginDTO);
String url = code2SessionUrl +"?appid=" + wxHsyProperties.getAppId() +
"&secret="+ wxHsyProperties.getAppSecret() +
"&js_code=" + wxLoginDTO.getCode() +
"&grant_type=authorization_code";
String jsonStr = HttpUtil.get(url, 30000);
log.info("WXserver code2Session return {}", jsonStr);
JSONObject jsonObject = JSONUtil.parseObj(jsonStr);
if (StrUtil.contains(jsonStr, "errcode")) {
throw new BizIllegalException("微信登录认证失败");
}
String openId = jsonObject.getStr("openid");
String sessionKey = jsonObject.getStr("session_key");
// String unionId = jsonObject.getStr("unionid");
// 解密得到用户手机号
String userInfo = WeChatUtil.decryptData(wxLoginDTO.getEncryptedData(), sessionKey, wxLoginDTO.getIv());
// 根据openId查找回收员 userType todo
Recycler recycler = recyclerService.getRecyclerByOpenid(openId);
if (recycler == null) {
// 回收员不存在,则注册
if (StringUtils.isEmpty(userInfo)) {
throw new BizIllegalException("解密用户信息错误");
}
JSONObject userObject = JSONUtil.parseObj(userInfo);
String phoneNumber = userObject.getStr("phoneNumber");
recycler = recyclerService.registerRecycler(phoneNumber, openId);
}
UserDTO userDTO = new UserDTO();
userDTO.setId(recycler.getId());
userDTO.setUserType(1); // todo
userDTO.setUsername(recycler.getStaffsName());
userDTO.setPhone(recycler.getMobilePhone());
String token = jwtTool.createToken(userDTO);
userDTO.setToken(token);
return CommonResult.success(userDTO);
}
@ApiOperation("退出")
@GetMapping("/logout")
public CommonResult<String> logout() {
return CommonResult.success("login success");
}
@ApiOperation("用户查询接口")
@GetMapping("/search")
public CommonResult<String> search() {
return CommonResult.success("users...");
}
}

@ -0,0 +1,32 @@
package cc.yunxi.controller;
import cc.yunxi.common.domain.PageDTO;
import cc.yunxi.common.domain.PageQuery;
import cc.yunxi.domain.dto.RecyclerDTO;
import cc.yunxi.domain.po.Recycler;
import cc.yunxi.service.IRecyclerService;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Api(tags = "回收员接口")
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class RecyclerController {
private final IRecyclerService recyclerService;
@ApiOperation("分页查询商品")
@GetMapping("/page")
public PageDTO<RecyclerDTO> queryItemByPage(PageQuery query) {
// 1.分页查询
Page<Recycler> result = recyclerService.page(query.toMpPage("good_total", false));
// 2.封装并返回
return PageDTO.of(result, RecyclerDTO.class);
}
}

@ -0,0 +1,50 @@
package cc.yunxi.domain.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
@Data
@ApiModel(description = "回收员实体")
public class RecyclerDTO {
@ApiModelProperty("回收员id")
private Integer id;
@ApiModelProperty("所属回收站id")
private String stationId; // 所属回收站
@ApiModelProperty("回收员姓名")
private String staffsName; // 回收员姓名
@ApiModelProperty("回收员电话")
private String mobilePhone; // 回收员电话
@ApiModelProperty("头像")
private String headIcon; // 头像
@ApiModelProperty("性别")
private Integer gender; // 性别
@ApiModelProperty("总里程数")
private Double recycleMiles; // 总里程数
@ApiModelProperty("总订单数")
private Integer orderTotal; // 总订单数
@ApiModelProperty("好评数")
private Integer goodTotal; // 好评数
@ApiModelProperty("总回收金额")
private BigDecimal orderAmount; // 总回收金额
@ApiModelProperty("自动接单(0-否1-是)")
private Integer autoEnabled; // 自动接单(0-否1-是)
@ApiModelProperty("状态(0-禁用1-启用)")
private Integer status; // 状态(0-禁用1-启用)
@ApiModelProperty("openid")
private String openid; // wx openid
}

@ -0,0 +1,21 @@
package cc.yunxi.domain.dto;
import lombok.Data;
/**
* DTO
*/
@Data
public class UserDTO {
private Integer userType; // 1 回收员 2 散户
private String id;
private String username;
private String phone;
private String token; // 访问token
}

@ -0,0 +1,11 @@
package cc.yunxi.domain.dto.form;
import lombok.Data;
@Data
public class WxLoginDTO {
private String code;
private String encryptedData;
private String iv;
private Integer userType; // 1 回收员 2 散户
}

@ -0,0 +1,115 @@
package cc.yunxi.domain.po;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.math.BigDecimal;
import java.util.Date;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
*
* </p>
*
* @author ccongli
* @since 2024-02-28 06:08:09
*/
@Getter
@Setter
@TableName("nx_client")
@ApiModel(value = "Client对象", description = "散户信息")
public class Client {
@ApiModelProperty("自然主键")
@TableId("id")
private String id;
@ApiModelProperty("微信openid")
@TableField("wx_openid")
private String wxOpenid;
@ApiModelProperty("微信名")
@TableField("wx_user_name")
private String wxUserName;
@ApiModelProperty("昵称")
@TableField("nick_name")
private String nickName;
@ApiModelProperty("手机")
@TableField("mobile_phone")
private String mobilePhone;
@ApiModelProperty("头像")
@TableField("head_icon")
private String headIcon;
@ApiModelProperty("性别")
@TableField("gender")
private Integer gender;
@ApiModelProperty("生日")
@TableField("birthday")
private Date birthday;
@ApiModelProperty("账户余额")
@TableField("banlance")
private BigDecimal banlance;
@ApiModelProperty("会员码")
@TableField("membership_number")
private String membershipNumber;
@ApiModelProperty("会员积分")
@TableField("membership_point")
private Integer membershipPoint;
@ApiModelProperty("会员等级")
@TableField("membership_level")
private Integer membershipLevel;
@ApiModelProperty("消费喜好")
@TableField("consume_preference")
private String consumePreference;
@ApiModelProperty("发票抬头")
@TableField("tax_title")
private String taxTitle;
@ApiModelProperty("企业税号")
@TableField("tax_id")
private String taxId;
@ApiModelProperty("当前位置-经度")
@TableField("longitude")
private Double longitude;
@ApiModelProperty("当前位置-纬度")
@TableField("latitude")
private Double latitude;
@ApiModelProperty("最后登录时间")
@TableField("last_log_time")
private Date lastLogTime;
@ApiModelProperty("最后登录IP")
@TableField("last_log_ip")
private String lastLogIp;
@ApiModelProperty("有效标志(0-禁用1-启用)")
@TableField("enabled_mark")
private Integer enabledMark;
@ApiModelProperty("创建人")
@TableField("creator_user_id")
private String creatorUserId;
@ApiModelProperty("创建时间")
@TableField("creator_time")
private Date creatorTime;
}

@ -0,0 +1,48 @@
package cc.yunxi.domain.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.math.BigDecimal;
/**
*
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("nx_recycle_station_staff")
public class Recycler {
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
private String stationId; // 所属回收站
private String staffsName; // 回收员姓名
private String mobilePhone; // 回收员电话
private String headIcon; // 头像
private Integer gender; // 性别
private Double recycleMiles; // 总里程数
private Integer orderTotal; // 总订单数
private Integer goodTotal; // 好评数
private BigDecimal orderAmount; // 总回收金额
private Integer autoEnabled; // 自动接单(0-否1-是)
@TableField("enabled_mark")
private Integer status; // 状态(0-禁用1-启用)
private String openid; // wx openid
}

@ -0,0 +1,22 @@
package cc.yunxi.domain.query;
import cc.yunxi.common.domain.PageQuery;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel(description = "回收员查询条件")
public class RecyclerQuery extends PageQuery {
@ApiModelProperty("所属回收站")
private String stationId;
@ApiModelProperty("联系电话")
private String mobilePhone;
@ApiModelProperty("回收员姓名")
private String staffsName;
}

@ -0,0 +1,40 @@
package cc.yunxi.interceptor;
import cc.yunxi.domain.dto.UserDTO;
import cc.yunxi.utils.JwtTool;
import cc.yunxi.utils.UserContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RequiredArgsConstructor
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
private final JwtTool jwtTool;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.获取请求头中的 token
String token = request.getHeader("authorization");
// 2.校验token
UserDTO userInfo = jwtTool.parseToken(token);
log.info("当前用户信息: 【{}】", userInfo);
// 3.存入上下文
UserContext.setUser(userInfo);
// 4.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清理用户
UserContext.removeUser();
}
}

@ -0,0 +1,36 @@
package cc.yunxi.interceptor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
@RequiredArgsConstructor
@Slf4j
public class StatsInterceptor implements HandlerInterceptor {
public static final String METHOD_REQUEST_ELAPSED_TIME = "method_request_elapsed_time";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
request.setAttribute(METHOD_REQUEST_ELAPSED_TIME, System.currentTimeMillis());
String requestURI = request.getRequestURI();
String method = request.getMethod();
String contentType = request.getContentType();
log.info("请求路径:【{}】, 请求方法:【{}】, 协议:【{}】", requestURI, method, contentType);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
long startMillis = (long) request.getAttribute(METHOD_REQUEST_ELAPSED_TIME);
long endMillis = System.currentTimeMillis();
log.info("请求耗时:【{}】ms", endMillis - startMillis);
}
}

@ -0,0 +1,18 @@
package cc.yunxi.mapper;
import cc.yunxi.domain.po.Client;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper
* </p>
*
* @author ccongli
* @since 2024-02-28 06:08:09
*/
@Mapper
public interface ClientMapper extends BaseMapper<Client> {
}

@ -0,0 +1,14 @@
package cc.yunxi.mapper;
import cc.yunxi.domain.po.Recycler;
import cc.yunxi.domain.po.Test;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* mapper
*/
@Mapper
public interface RecyclerMapper extends BaseMapper<Recycler> {
}

@ -0,0 +1,16 @@
package cc.yunxi.service;
import cc.yunxi.domain.po.Client;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
*
* </p>
*
* @author ccongli
* @since 2024-02-28 06:08:09
*/
public interface ClientService extends IService<Client> {
}

@ -0,0 +1,23 @@
package cc.yunxi.service;
import cc.yunxi.domain.po.Recycler;
import cc.yunxi.domain.po.Test;
import com.baomidou.mybatisplus.extension.service.IService;
/**
*
*/
public interface IRecyclerService extends IService<Recycler> {
/**
* openid
* @param openid
* @return Recycler
*/
Recycler getRecyclerByOpenid(String openid);
/**
*
*/
Recycler registerRecycler(String phoneNumber, String openId);
}

@ -0,0 +1,20 @@
package cc.yunxi.service.impl;
import cc.yunxi.domain.po.Client;
import cc.yunxi.mapper.ClientMapper;
import cc.yunxi.service.ClientService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
*
* </p>
*
* @author ccongli
* @since 2024-02-28 06:08:09
*/
@Service
public class ClientServiceImpl extends ServiceImpl<ClientMapper, Client> implements ClientService {
}

@ -0,0 +1,46 @@
package cc.yunxi.service.impl;
import cc.yunxi.common.exception.BizIllegalException;
import cc.yunxi.domain.po.Recycler;
import cc.yunxi.mapper.RecyclerMapper;
import cc.yunxi.service.IRecyclerService;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
/**
*
*/
@Service
public class RecyclerServiceImpl extends ServiceImpl<RecyclerMapper, Recycler> implements IRecyclerService {
@Resource
RecyclerMapper recyclerMapper;
@Override
public Recycler getRecyclerByOpenid(String openid) {
Recycler recycler = lambdaQuery().eq(Recycler::getOpenid, openid).one();
return recycler;
}
@Override
@Transactional
public Recycler registerRecycler(String phoneNumber, String openId) {
// 判断手机号是否被注册
Recycler recycler = lambdaQuery().eq(Recycler::getMobilePhone, phoneNumber).one();
if (ObjectUtil.isNotEmpty(recycler)) {
throw new BizIllegalException("该微信号已被注册");
}
recycler = new Recycler();
recycler.setMobilePhone(phoneNumber);
recycler.setStaffsName(phoneNumber);
recycler.setOpenid(openId);
recyclerMapper.insert(recycler);
return recycler;
}
}

@ -0,0 +1,100 @@
package cc.yunxi.utils;
import cc.yunxi.common.exception.UnauthorizedException;
import cc.yunxi.config.JwtProperties;
import cc.yunxi.domain.dto.UserDTO;
import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTValidator;
import cn.hutool.jwt.signers.JWTSigner;
import cn.hutool.jwt.signers.JWTSignerUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import java.security.KeyPair;
import java.time.Duration;
import java.util.Date;
@Component
public class JwtTool {
private final JWTSigner jwtSigner;
@Autowired
@Lazy
private JwtProperties jwtProperties;
public JwtTool(KeyPair keyPair) {
this.jwtSigner = JWTSignerUtil.createSigner("rs256", keyPair);
}
/**
* access-token
*
* @param userDTO
* @return access-token
*/
public String createToken(UserDTO userDTO) {
// 1.生成jws
return JWT.create()
.setPayload("user", userDTO)
.setExpiresAt(new Date(System.currentTimeMillis() + jwtProperties.getTokenTTL().toMillis()))
.setSigner(jwtSigner)
.sign();
}
/**
* token
*/
public JWT verifyToken(String token) {
// 1.校验token是否为空
if (token == null) {
throw new UnauthorizedException("请先登录");
}
// 2.校验jwt
JWT jwt;
try {
jwt = JWT.of(token).setSigner(jwtSigner);
} catch (Exception e) {
throw new UnauthorizedException("无效的token", e);
}
// 2.校验jwt是否有效
if (!jwt.verify()) {
// 验证失败
throw new UnauthorizedException("无效的token");
}
// 3.校验是否过期
try {
JWTValidator.of(jwt).validateDate();
} catch (ValidateException e) {
throw new UnauthorizedException("token已过期");
}
return jwt;
}
/**
* token
*
* @param token token
* @return token
*/
public UserDTO parseToken(String token) {
JWT jwt = verifyToken(token);
// 2.数据格式校验
JSONObject userPayload = (JSONObject) jwt.getPayload("user");
if (userPayload == null) {
// 数据为空
throw new UnauthorizedException("无效的token");
}
// 3.数据解析
try {
return JSONUtil.toBean(userPayload, UserDTO.class);
} catch (RuntimeException e) {
// 数据格式有误
throw new UnauthorizedException("无效的token", e);
}
}
}

@ -1,21 +1,26 @@
package cc.yunxi.common.utils;
package cc.yunxi.utils;
import cc.yunxi.domain.dto.UserDTO;
/**
*
*/
public class UserContext {
private static final ThreadLocal<Long> tl = new ThreadLocal<>();
private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();
/**
* ThreadLocal
* @param userId id
* @param userInfo id
*/
public static void setUser(Long userId) {
tl.set(userId);
public static void setUser(UserDTO userInfo) {
tl.set(userInfo);
}
/**
*
* @return id
*/
public static Long getUser() {
public static UserDTO getUser() {
return tl.get();
}

@ -0,0 +1,173 @@
package cc.yunxi.utils;
import cn.hutool.core.codec.Base64;
import cn.hutool.json.JSONObject;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.Security;
import java.util.Arrays;
/**
*
*
* @author zhuhuix
* @date 2019-12-25
*/
public class WeChatUtil {
private static final String KEY_ALGORITHM = "AES";
private static final String ALGORITHM_STR = "AES/CBC/PKCS7Padding";
private static Key key;
private static Cipher cipher;
private static void init(byte[] keyBytes) {
// 如果密钥不足16位那么就补足. 这个if 中的内容很重要
int base = 16;
if (keyBytes.length % base != 0) {
int groups = keyBytes.length / base + 1;
byte[] temp = new byte[groups * base];
Arrays.fill(temp, (byte) 0);
System.arraycopy(keyBytes, 0, temp, 0, keyBytes.length);
keyBytes = temp;
}
// 初始化
Security.addProvider(new BouncyCastleProvider());
// 转化成JAVA的密钥格式
key = new SecretKeySpec(keyBytes, KEY_ALGORITHM);
try {
// 初始化cipher
cipher = Cipher.getInstance(ALGORITHM_STR, "BC");
} catch (Exception e) {
e.printStackTrace();
}
}
public static String decryptData(String encryptDataB64, String sessionKeyB64, String ivB64) {
return new String(
decryptOfDiyIv(
Base64.decode(encryptDataB64),
Base64.decode(sessionKeyB64),
Base64.decode(ivB64)
)
);
}
/**
*
*
* @param encryptedData
* @param keyBytes
* @param ivs iv
* @return
*/
private static byte[] decryptOfDiyIv(byte[] encryptedData, byte[] keyBytes, byte[] ivs) {
byte[] encryptedText = null;
init(keyBytes);
try {
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivs));
encryptedText = cipher.doFinal(encryptedData);
} catch (Exception e) {
e.printStackTrace();
}
return encryptedText;
}
public static String httpRequest(String requestUrl, String requestMethod, String output) {
try {
URL url = new URL(requestUrl);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setRequestMethod(requestMethod);
if (null != output) {
OutputStream outputStream = connection.getOutputStream();
outputStream.write(output.getBytes(StandardCharsets.UTF_8));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = connection.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str;
StringBuilder buffer = new StringBuilder();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
connection.disconnect();
return buffer.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
/**
* URL POST
*
* @param url URL
* @param json json
* @return
*/
public static String httpPost(String url, JSONObject json) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
URL realUrl = new URL(url);
// 打开和URL之间的连接
URLConnection conn = realUrl.openConnection();
// 设置通用的请求属性
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 发送POST请求必须设置如下两行
conn.setDoOutput(true);
conn.setDoInput(true);
// 获取URLConnection对象对应的输出流
out = new PrintWriter(conn.getOutputStream());
// 发送请求参数
out.print(json);
// flush输出流的缓冲
out.flush();
// 定义BufferedReader输入流来读取URL的响应
in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result=result.concat(line);
}
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!" + e);
e.printStackTrace();
}
//使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
}
}

@ -1,5 +1,7 @@
server:
port: 8080
servlet:
context-path: /api
spring:
application:
@ -56,16 +58,18 @@ knife4j:
api-rule: package
api-rule-resources:
- cc.yunxi.controller
#hm:
# jwt:
# location: classpath:hmall.jks
# alias: hmall
# password: hmall123
# tokenTTL: 30m
# auth:
# excludePaths:
# - /search/**
# - /users/login
# - /items/**
# - /hi
# keytool -genkeypair -alias hmall -keyalg RSA -keypass hmall123 -keystore hmall.jks -storepass hmall123
nxhs:
jwt:
location: classpath:nxhs.jks
alias: nxhs
password: nxhs2024
tokenTTL: 1h
auth:
excludePaths:
- /common/login
- /test/**
wx:
recycler: # 回收员端
appid: wxf82bcc798891a29d
appsecret: f37fb0ab2b5f691d8507acced60a58fb
# keytool -genkeypair -alias nxhs -keyalg RSA -keypass nxhs2024 -keystore nxhs.jks -storepass nxhs2024

@ -177,28 +177,6 @@
</filter>
</appender>
<!--root配置必须在appender下边-->
<!--root节点是对所有appender的管理,添加哪个appender就会打印哪个appender的日志-->
<!--root节点的level是总的日志级别控制,如果appender的日志级别设定比root的高,会按照appender的日志级别打印日志,-->
<!--如果appender的日志级别比root的低,会按照root设定的日志级别进行打印日志-->
<!--也就是说root设定的日志级别是最低限制,如果root设定级别为最高ERROR,那么所有appender只能打印最高级别的日志-->
<!-- <root level="INFO">-->
<!-- <appender-ref ref="STDOUT" />-->
<!-- <appender-ref ref="DEBUG_FILE" />-->
<!-- <appender-ref ref="INFO_FILE" />-->
<!-- <appender-ref ref="WARN_FILE" />-->
<!-- <appender-ref ref="ERROR_FILE" />-->
<!-- </root>-->
<!--name:用来指定受此loger约束的某一个包或者具体的某一个类。-->
<!--addtivity:是否向上级loger传递打印信息。默认是true。-->
<!-- <logger name="com.pikaiqu.logbackdemo.LogbackdemoApplicationTests" level="debug" additivity="false">-->
<!-- <appender-ref ref="STDOUT" />-->
<!-- <appender-ref ref="INFO_FILE" />-->
<!-- </logger>-->
<!--配置多环境日志输出 可以在application.properties中配置选择哪个profiles : spring.profiles.active=dev-->
<!--生产环境:输出到文件-->
<springProfile name="pro">
<root level="info">

@ -0,0 +1,5 @@
<?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="cc.yunxi.mapper.ClientMapper">
</mapper>

@ -0,0 +1,5 @@
<?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="cc.yunxi.mapper.ClientMapper">
</mapper>

@ -1,10 +1,13 @@
package cc.yunxi;
import cc.yunxi.domain.dto.UserDTO;
import cc.yunxi.service.ITestService;
import cc.yunxi.utils.JwtTool;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.time.Duration;
import java.util.List;
@SpringBootTest
@ -13,9 +16,24 @@ public class NxhsApplicationTest {
@Resource
ITestService testService;
@Resource
JwtTool jwtTool;
@Test
void test01() {
List<cc.yunxi.domain.po.Test> list = testService.list();
System.out.println(list);
}
@Test
void test02() {
UserDTO userDTO = new UserDTO();
userDTO.setId("10086");
userDTO.setPhone("15137603460");
userDTO.setUsername("小冲");
String token = jwtTool.createToken(userDTO);
UserDTO userInfo = jwtTool.parseToken(token);
System.out.println(userInfo);
}
}

@ -23,9 +23,10 @@
<spring-boot.version>2.7.6</spring-boot.version>
<mybatis-plus.version>3.4.3</mybatis-plus.version>
<hutool.version>5.8.11</hutool.version>
<knife4j-openapi2.version>4.1.0</knife4j-openapi2.version>
<security-rsa.version>1.0.9.RELEASE</security-rsa.version>
</properties>
<!-- 版本统一管理 -->
<dependencyManagement>
<dependencies>
@ -37,59 +38,49 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- 数据库相关依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- swagger -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>${knife4j-openapi2.version}</version>
</dependency>
<!-- 加密 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-rsa</artifactId>
<version>${security-rsa.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 数据库相关依赖 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--redis-->
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--json处理-->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 常用扩展 -->
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<!-- swagger -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.1.0</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
@ -125,7 +116,6 @@
</plugins>
</build>
<repositories>
<repository>
<id>alimaven</id>

Loading…
Cancel
Save