Ip2regionEntity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("ip2region entity")
public class Ip2regionEntity {
@ApiModelProperty("国家")
@NotNull
private String country;
@ApiModelProperty("区域")
private String district;
@ApiModelProperty("省份")
private String province;
@ApiModelProperty("市")
private String city;
@ApiModelProperty("运营商")
private String operators;
@ApiModelProperty("内网")
private Boolean isInnerIp;
@ApiModelProperty("ip")
private String ip;
@ApiModelProperty("long ip")
private Long iplong;
@ApiModelProperty("raw")
private String raw;
public Ip2regionEntity(Boolean isInnerIp) {
this.isInnerIp = isInnerIp;
}
public static Ip2regionEntity innerIp() {
return new Ip2regionEntity(true);
}
@Override
public String toString() {
return raw;
}
}
UserEntity
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "user", autoResultMap = true)
@Builder
@ApiModel(description = "用户实体对象")
public class UserEntity implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
......
@ApiModelProperty("ipv4 ipv6 数字地址")
@TableField(fill = FieldFill.INSERT)
private Long ip;
@ApiModelProperty("ip 字符地址 国家|区域|省|市|宽带")
@TableField(typeHandler = Ip2regionTypeHandler.class, jdbcType = JdbcType.VARCHAR, fill = FieldFill.INSERT)
// @JsonSerialize(using = ToStringSerializer.class)
private Ip2regionEntity ipAddr;
}
Ip2regionConfig
@Configuration
public class Ip2regionConfig {
@Value("classpath:ip2region.xdb")
private Resource xdb;
@Bean(destroyMethod = "close")
public Searcher search() throws IOException {
byte[] db = xdb.getInputStream().readAllBytes();
return Searcher.newWithBuffer(db);
}
}
Ip2regionTypeUtil
package com.bluereba.pojo.utils;
import cn.hutool.core.net.NetUtil;
import com.bluereba.pojo.handler.Ip2regionEntity;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
/**
* 工具类
*
* @author 叶大于
* @since 2023/5/14 18:06
*/
public class Ip2regionTypeUtil {
private static boolean check(String addr) {
return !(StringUtils.isNotBlank(addr) && StringUtils.countMatches(addr, "|") == 5);
}
private static String[] safeSplit(String addr) {
String[] split = StringUtils.split(addr, "|", 6);
return ObjectUtils.isEmpty(split) ? new String[6] : split;
}
/**
* 构建 Ip2region 实体
*
* @param addr ip地址
* @return 构建实体
*/
public static @NonNull Ip2regionEntity buildEntity(@Nullable String addr) {
if (check(addr)) {
return new Ip2regionEntity();
}
String[] split = safeSplit(addr);
long ipv4 = NetUtil.ipv4ToLong(split[5]);
return Ip2regionEntity.builder()
.isInnerIp(false)
.country(split[0])
.district(split[1])
.province(split[2])
.city(split[3])
.operators(split[4])
.ip(split[5])
.iplong(ipv4)
.raw(addr)
.build();
}
/**
* 有效 ip2region results 拼接 += |ip
* @param addr ip2region results
* @param ip ip地址
* @return 完整格式
*/
public static String joinIp(@Nullable String addr, @Nullable String ip) {
if (check(addr)) {
return addr + "|" + ip;
}
return addr;
}
public static String joinIp(@Nullable String addr, @Nullable Long ip) {
if (check(addr) && ObjectUtils.isNotEmpty(ip)) {
return addr + "|" + NetUtil.longToIpv4(ip);
}
return addr;
}
public static String toString(@Nullable Ip2regionEntity entity) {
return ObjectUtils.isEmpty(entity) ? null : entity.toString();
}
}
IpSearcher
@Component
@RequiredArgsConstructor
public class IpSearcher {
private final Searcher searcher;
/**
* 查询 ip地址
* @param ip 必须符合 ip4 格式 (@Ipv4)
* @return Ip2regionEntity
*/
public Ip2regionEntity search(@NonNull String ip) {
try {
String addr = searcher.search(ip);
String fullAddr = Ip2regionTypeUtil.joinIp(addr, ip);
return Ip2regionTypeUtil.buildEntity(fullAddr);
} catch (Exception e) {
throw new IpSearchException();
}
}
/**
* 查询 ip地址
* @param ip 必须符合 ip number 格式
* @return Ip2regionEntity
*/
public Ip2regionEntity search(@NonNull Long ip) {
try {
String addr = searcher.search(ip);
String fullAddr = Ip2regionTypeUtil.joinIp(addr, ip);
return Ip2regionTypeUtil.buildEntity(fullAddr);
} catch (Exception e) {
throw new IpSearchException();
}
}
}
Ip2regionTypeHandler
数据库中存储格式 中国|0|河南省|焦作市|联通|42.226.158.210
依赖于Mybatis,通过 setNonNullParameter 存储至数据库
通过 getNullableResult 在 java 中获取 Ip2regionEntity 对象
@Component
@MappedJdbcTypes(JdbcType.VARCHAR)
public class Ip2regionTypeHandler extends BaseTypeHandler<Ip2regionEntity> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Ip2regionEntity entity, JdbcType jt)
throws SQLException {
ps.setObject(i, Ip2regionTypeUtil.toString(entity));
}
@Override
public Ip2regionEntity getNullableResult(ResultSet resultSet, String s) throws SQLException {
String addr = resultSet.getString(s);
return Ip2regionTypeUtil.buildEntity(addr);
}
@Override
public Ip2regionEntity getNullableResult(ResultSet resultSet, int i) throws SQLException {
String addr = resultSet.getString(i);
return Ip2regionTypeUtil.buildEntity(addr);
}
@Override
public Ip2regionEntity getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
String addr = callableStatement.getString(i);
return Ip2regionTypeUtil.buildEntity(addr);
}
}
Mybatis Plus 自动填充
@Slf4j
@Component
@RequiredArgsConstructor
public class MyMetaObjectHandler implements MetaObjectHandler {
private final IpSearchService ipSearchService;
private final HttpServletRequest request;
@Override
public void insertFill(MetaObject meta) {
// TODO add insert operate user info
this.strictInsertFill(meta, "createAt", LocalDateTime::now, LocalDateTime.class);
this.strictInsertFill(meta, "updateAt", LocalDateTime::now, LocalDateTime.class);
// 模拟非内网
addressFill(meta, "42.226.158.210");
// addressFill(meta, ServletUtil.getClientIP(request));
}
@Override
public void updateFill(MetaObject metaObject) {
// TODO add update operate user info
this.strictUpdateFill(metaObject, "updateAt", LocalDateTime::now, LocalDateTime.class);
}
private void addressFill(MetaObject metaObject, String ip) {
ipSearchService.getAddressOpt(ip).ifPresent(address -> {
this.strictInsertFill(metaObject, "ip", address::getIplong, Long.class);
this.strictInsertFill(metaObject, "ipAddr", () -> address, Ip2regionEntity.class);
});
}
}
Test
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class IpSearchTest {
@Autowired
IpSearchService ipSearchService;
@Test
public void getAddress() {
ipSearchService.getAddressOpt("14.117.140.125").ifPresent(System.out::println);
// 中国|0|广东省|江门市|电信|14.117.140.125
}
@Test
public void longToAddress() {
ipSearchService.getAddressOpt(24251L).ifPresent(System.out::println);
// 0|0|0|内网IP|内网IP|0.0.94.187
}
}
View
POST /user/add
body---
{
"username": "张敏^张敏^张敏^张敏",
"password": "aute aliquip",
"email": "i.kgsz@qq.com"
}
@PostMapping("add")
@ApiOperation("添加用户")
public UserEntity addUser(@RequestBody @Valid AddUserVo vo) {
String username = vo.getUsername();
String password = vo.getPassword();
String email = vo.getEmail();
UserEntity entity = UserEntity.builder()
.username(username)
.password(password)
.email(email)
.build();
userService.save(entity);
return entity;
}
@Data
@ApiModel(description = "添加用户vo")
public class AddUserVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Size(min = 6, max = 12)
@NotEmpty(message = "用户名不能为空")
@ApiModelProperty("用户名")
private String username;
@Size(min = 6, max = 12)
@NotNull(message = "用户密码不能为空")
@ApiModelProperty("用户密码")
private String password;
@Email
@ApiModelProperty("用户邮件")
private String email;
}
{
"status": 200,
"message": "请求成功",
"data": {
"nickname": null,
"id": 1657809333670019073,
"username": "张敏^张敏^张敏^张敏",
"email": "i.kgsz@qq.com",
"address": null,
"gender": null,
"icon": null,
"age": null,
"createAt": "2023-05-15T02:05:20.4228379",
"updateAt": "2023-05-15T02:05:20.4238371",
"createBy": null,
"updateBy": null,
"isStatus": null,
"isDeleted": null,
"remark": null,
"version": null,
"ip": 719494866,
"ipAddr": {
"country": "中国",
"district": "0",
"province": "河南省",
"city": "焦作市",
"operators": "联通",
"isInnerIp": false,
"ip": "42.226.158.210",
"iplong": 719494866,
"raw": "中国|0|河南省|焦作市|联通|42.226.158.210"
}
}
}