SpringBoot AOP完美记录用户操作日志,附源码
记录内容
- 接口名称
- 浏览器名称
- 操作系统
- 请求ip
- 接口入参、出参
- 接口耗时
- 。。。。
表结构
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0; -- ----------------------------
-- Table structure for sys_log
-- ----------------------------
DROP TABLE IF EXISTS `sys_log`;
CREATE TABLE `sys_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`module_name` varchar(256) DEFAULT NULL COMMENT '模块名称',
`browser_name` varchar(1024) DEFAULT NULL COMMENT '浏览器名称',
`os_name` varchar(256) DEFAULT NULL COMMENT '操作系统名称',
`ip_addr` varchar(256) DEFAULT NULL COMMENT '请求ip',
`app_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '服务名称',
`class_name` varchar(1024) DEFAULT NULL COMMENT '类名',
`method_name` varchar(512) DEFAULT NULL COMMENT '方法',
`request_url` varchar(1024) DEFAULT NULL COMMENT '请求url',
`request_method` varchar(255) DEFAULT NULL COMMENT '请求方式,POST、GET',
`request_param` text COMMENT '请求参数',
`result_text` text CHARACTER SET utf8 COLLATE utf8_general_ci COMMENT '响应参数',
`status` tinyint(1) DEFAULT NULL COMMENT '接口状态(0成功 1失败)',
`error_text` text COMMENT '错误信息',
`take_up_time` varchar(64) DEFAULT NULL COMMENT '耗时',
`edit_table_id` bigint(20) DEFAULT NULL COMMENT '编辑的表主键,只有修改时才有值',
`edit_table_name` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '编辑的表名称,只有修改时才有值',
`create_time` datetime DEFAULT NULL COMMENT '操作时间',
`create_user_id` bigint(20) DEFAULT NULL COMMENT '创建人id',
`create_phone_number` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '创建人手机号',
`create_user_name` varchar(64) DEFAULT NULL COMMENT '创建人姓名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='系统操作日志'; SET FOREIGN_KEY_CHECKS = 1;
sys_log.sql
添加依赖
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- 获取浏览器信息 -->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.21</version>
</dependency>
自定义注解(一)
import java.lang.annotation.*; @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
}
备注:被该注解修饰的方法,会被记录到日志中
自定义注解(二)
import java.lang.annotation.*; @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogPlus {
/**
* 编辑的表主键
* @return
*/
String editTableId() default "id"; /**
* 编辑的表名称
* @return
*/
String editTableName() default "未知";
}
备注:被该注解修饰的类,会记录从表的id值和从表的表名(用于记录某张表的一行记录,历史修改信息,不需要可忽略)
日志表实体类
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import java.io.Serializable;
import java.util.Date; /**
* <p>
* 系统操作日志
* </p>
*
* @author chenyanbin
* @since 2021-10-13
*/
@Data
@TableName("sys_log")
@ApiModel(value = "SysLogDO对象", description = "系统操作日志")
public class LogDO implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "主键")
@TableId(value = "id", type = IdType.AUTO)
private Long id; @ApiModelProperty(value = "模块名称")
private String moduleName; @ApiModelProperty(value = "浏览器名称")
private String browserName; @ApiModelProperty(value = "操作系统名称")
private String osName; @ApiModelProperty(value = "请求ip")
private String ipAddr; @ApiModelProperty(value = "服务名称")
private String appName; @ApiModelProperty(value = "类名")
private String className; @ApiModelProperty(value = "方法")
private String methodName; @ApiModelProperty(value = "请求url")
private String requestUrl; @ApiModelProperty(value = "请求方式,POST、GET")
private String requestMethod; @ApiModelProperty(value = "请求参数")
private String requestParam; @ApiModelProperty(value = "响应参数")
private String resultText; @ApiModelProperty(value = "接口状态(0成功 1失败)")
private Byte status; @ApiModelProperty(value = "错误信息")
private String errorText; @ApiModelProperty(value = "耗时")
private String takeUpTime; @ApiModelProperty(value = "编辑的表主键,只有修改时才有值")
private Long editTableId; @ApiModelProperty(value = "编辑的表名称,只有修改时才有值")
private String editTableName; @ApiModelProperty(value = "操作时间")
private Date createTime; @ApiModelProperty(value = "创建人id")
private Long createUserId; @ApiModelProperty(value = "创建人手机号")
private String createPhoneNumber; @ApiModelProperty(value = "创建人姓名")
private String createUserName; }
Mapper.java
import com.baomidou.mybatisplus.core.mapper.BaseMapper; public interface LogMapper extends BaseMapper<LogDO> {
}
Aspect (AOP)
import com.alibaba.fastjson.JSON;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder; import javax.servlet.http.HttpServletRequest; /**
* 操作日志处理
*
* @Author:chenyanbin
*/
@Slf4j
@Aspect
@Component
public class LogAspect {
@Autowired
LogMapper logMapper;
@Value("${spring.application.name}")
private String appNname; @Pointcut("@annotation(com.yida.annotation.Log)")
public void logPoint() {
} @Around("logPoint()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = null;
LogDO logDO = new LogDO();
try {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = (HttpServletRequest) requestAttributes
.resolveReference(RequestAttributes.REFERENCE_REQUEST);
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
//浏览器对象
Browser browser = userAgent.getBrowser();
//操作系统对象
OperatingSystem operatingSystem = userAgent.getOperatingSystem();
logDO.setBrowserName(browser.getName());
ApiOperation aon = methodSignature.getMethod().getAnnotation(ApiOperation.class);
if (aon != null) {
logDO.setModuleName(aon.value());
}
logDO.setOsName(operatingSystem.getName());
logDO.setIpAddr(CommonUtil.getIpAddr(request));
logDO.setAppName(appNname);
logDO.setClassName(joinPoint.getTarget().getClass().getName());
logDO.setMethodName(methodSignature.getMethod().getName());
logDO.setRequestUrl(request.getRequestURI());
logDO.setRequestMethod(request.getMethod());
//获取请求参数
CommonUtil.getRequestParam(joinPoint, methodSignature, logDO);
logDO.setResultText(JSON.toJSONString(result));
logDO.setStatus((byte) 0);
logDO.setCreateTime(CommonUtil.getCurrentDate());
logDO.setCreateUserId(CommonUtil.getCurrentUserId());
logDO.setCreatePhoneNumber(CommonUtil.getCurrentPhoneNumber());
logDO.setCreateUserName(CommonUtil.getCurrentUserName());
long startTime = System.currentTimeMillis();
result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
logDO.setTakeUpTime(String.format("耗时:%s 毫秒", endTime - startTime));
logDO.setResultText(result.toString());
} catch (Exception e) {
logDO.setStatus((byte) 1);
if (e instanceof BizException) {
BizException bizException = (BizException) e;
result = JsonData.buildCodeAndMsg(bizException.getCode(), bizException.getMessage());
logDO.setErrorText(result.toString());
} else if (e instanceof RpvException) {
RpvException ve = (RpvException) e;
result = JsonData.buildCodeAndMsg(ve.getCode(), ve.getMessage());
logDO.setErrorText(result.toString());
} else {
logDO.setErrorText(e.getMessage());
result = e.getMessage();
}
} finally {
logMapper.insert(logDO);
}
return result;
}
}
String agent=request.getHeader("User-Agent");
//解析agent字符串
UserAgent userAgent = UserAgent.parseUserAgentString(agent);
//获取浏览器对象
Browser browser = userAgent.getBrowser();
//获取操作系统对象
OperatingSystem operatingSystem = userAgent.getOperatingSystem(); System.out.println("浏览器名:"+browser.getName());
System.out.println("浏览器类型:"+browser.getBrowserType());
System.out.println("浏览器家族:"+browser.getGroup());
System.out.println("浏览器生产厂商:"+browser.getManufacturer());
System.out.println("浏览器使用的渲染引擎:"+browser.getRenderingEngine());
System.out.println("浏览器版本:"+userAgent.getBrowserVersion()); System.out.println("操作系统名:"+operatingSystem.getName());
System.out.println("访问设备类型:"+operatingSystem.getDeviceType());
System.out.println("操作系统家族:"+operatingSystem.getGroup());
System.out.println("操作系统生产厂商:"+operatingSystem.getManufacturer());
其他类
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.util.*; /**
* 公共工具类
*
* @Author:chenyanbin
*/
@Slf4j
public class CommonUtil {
private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); /**
* 获取ip
*
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request) {
String ipAddress = null;
try {
ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if (ipAddress.equals("127.0.0.1")) {
// 根据网卡取本机配置的IP
InetAddress inet = null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress = inet.getHostAddress();
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.length() > 15) {
// "***.***.***.***".length()
// = 15
if (ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
}
}
} catch (Exception e) {
ipAddress = "";
}
return ipAddress;
} /**
* 获取当前时间戳
*
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis();
} /**
* 获取当前日期
*
* @return
*/
public static Date getCurrentDate() {
return new Date();
} /**
* 获取当前操作用户的主键id
*
* @return
*/
public static Long getCurrentUserId() {
LoginUser loginUser = getLoginUser();
if (loginUser == null) {
return null;
}
return loginUser.getId();
} private static LoginUser getLoginUser() {
return JwtFilter.threadLocal.get();
} /**
* 获取当前操作用户的手机号
*
* @return
*/
public static String getCurrentPhoneNumber() {
LoginUser loginUser = getLoginUser();
if (loginUser == null) {
return null;
}
return loginUser.getPhoneNumber();
} /**
* 获取当前操作用户的名称
*
* @return
*/
public static String getCurrentUserName() {
LoginUser loginUser = getLoginUser();
if (loginUser == null) {
return null;
}
return loginUser.getUserName();
} /**
* 判断当前用户是否管理员
*/
public static boolean isAdmin() {
return getCurrentUserId() == 1;
} /**
* 获取请求参数
*
* @param joinPoint 切入点
* @param signature 方法签名
* @param logDO 日志对象
*/
public static void getRequestParam(ProceedingJoinPoint joinPoint, MethodSignature signature, LogDO logDO) {
// 参数值
Object[] args = joinPoint.getArgs();
ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
Method method = signature.getMethod();
String[] parameterNames = pnd.getParameterNames(method);
Map<String, Object> paramMap = new HashMap<>(32);
for (int i = 0; i < parameterNames.length; i++) {
paramMap.put(parameterNames[i], args[i]);
if (args[i] != null) {
//反射获取具体的值
LogPlus logPlus = args[i].getClass().getAnnotation(LogPlus.class);
if (logPlus != null) {
Field f = null;
try {
f = args[i].getClass().getDeclaredField(logPlus.editTableId());
f.setAccessible(true);
Object obj = f.get(args[i]);
logDO.setEditTableId(Long.valueOf(obj + ""));
logDO.setEditTableName(logPlus.editTableName());
} catch (Exception e) {
log.error("反射获取编辑的表主键异常:{}", e.getMessage());
} finally {
if (f != null) {
f.setAccessible(false);
}
}
}
}
}
logDO.setRequestParam(paramMap.toString());
}
}
CommonUtil.java
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data; /**
* 登录用户
* @Author:chenyanbin
*/
@Data
public class LoginUser {
/**
* 主键
*/
private Long id; /**
* 手机号
*/
@JsonProperty("phone_number")
private String phoneNumber; /**
* 用户昵称
*/
@JsonProperty("user_name")
private String userName; /**
* 是否货主(0是 1否)
*/
@JsonProperty("cargo_master")
private byte cargoMaster; /**
* 管理员 (0是 1否)
*/
private byte admin;
}
LoginUser.java
import lombok.Data; /**
* 业务异常类
* @Author:chenyanbin
*/
@Data
public class BizException extends RuntimeException {
private int code;
private String message; public BizException(int code, String message) {
super(message);
this.code = code;
this.message = message;
} public BizException(BizCodeEnum bizCodeEnum) {
super(bizCodeEnum.getMessage());
this.code = bizCodeEnum.getCode();
this.message = bizCodeEnum.getMessage();
}
}
BizException.java
import lombok.Data; /**
* Request Param Value:请求参数异常
* @Author:chenyanbin
*/
@Data
public class RpvException extends RuntimeException {
private int code;
private String message; public RpvException(int code, String message) {
this.code = code;
this.message = message;
}
}
RpvException.java
package com.yida.utils; import com.yida.enums.BizCodeEnum; import java.io.Serializable; /**
* @Description:统一协议JsonData工具类
* @Author:chenyanbin
* @Date:2021/5/9 下午8:09
* @Versiion:1.0
*/
public class JsonData<T> implements Serializable {
/**
* 状态码 0 表示成功,1表示处理中,-1表示失败
*/
private Integer code;
/**
* 数据
*/
private T data;
/**
* 描述
*/
private String msg; private JsonData() {
} private static <T> JsonData<T> build(Integer code, T data, String msg) {
JsonData json = new JsonData();
json.setCode(code);
json.setData(data);
json.setMsg(msg);
return json;
} /**
* 成功,传⼊数据
*
* @return
*/
public static <T> JsonData<T> buildSuccess() {
return build(0, (T) "", "");
} /**
* 默认添加成功
*
* @return
*/
public static <T> JsonData<T> buildAddSuccess() {
return build(0, (T) "添加成功", "");
} /**
* 默认修改成功
*
* @return
*/
public static <T> JsonData<T> buildEditSuccess() {
return build(0, (T) "修改成功", "");
} /**
* 默认删除成功
*
* @return
*/
public static <T> JsonData<T> buildRemoveSuccess() {
return build(0, (T) "删除成功", "");
} /**
* 成功,传⼊数据
*
* @param data
* @return
*/
public static <T> JsonData<T> buildSuccess(T data) {
return build(0, data, "");
} /**
* 失败,传⼊描述信息
*
* @param msg
* @return
*/
public static <T> JsonData<T> buildError(String msg) {
return build(-1, (T) "", msg);
} /**
* ⾃定义状态码和错误信息
*
* @param code
* @param msg
* @return
*/
public static <T> JsonData<T> buildCodeAndMsg(int code, String msg) {
return build(code, null, msg);
} /**
* 传⼊枚举,返回信息
*
* @param codeEnum
* @return
*/
public static <T> JsonData<T> buildResult(BizCodeEnum codeEnum) {
return buildCodeAndMsg(codeEnum.getCode(), codeEnum.getMessage());
} /**
* 判断接口响应是否成功,只是判断状态码是否等于:0
*
* @param data
* @return
*/
public static boolean isSuccess(JsonData data) {
return data.getCode() == 0;
} /**
* 判断接口响应是否失败,状态码除了0以外的,默认调用失败
*
* @param data
* @return
*/
public static boolean isFailure(JsonData data) {
return !isSuccess(data);
} public Integer getCode() {
return code;
} public void setCode(Integer code) {
this.code = code;
} public T getData() {
return data;
} public void setData(T data) {
this.data = data;
} public String getMsg() {
return msg;
} public void setMsg(String msg) {
this.msg = msg;
} @Override
public String toString() {
return "JsonData{" +
"code=" + code +
", data=" + data +
", msg='" + msg + '\'' +
'}';
}
}
JsonData.java
控制器添加@Log注解
@ApiOperation("用户登录")
@PostMapping("login")
@Log
public JsonData login(
@ApiParam("用户登录对象") @RequestBody UserLoginRequest userLoginRequest
) {
return userService.login(userLoginRequest);
}
修改接口,json对象添加@LogPlus
@Data
@ApiModel(value = "角色编辑对象", description = "用户编辑请求对象")
@LogPlus(editTableId = "id", editTableName = "sys_role")
public class RoleEditRequest {
@ApiModelProperty(value = "主键id", example = "-1")
@DecimalMin(value = "1", message = "角色id最小为1")
private Long id; @ApiModelProperty(value = "角色名称", example = "货主")
@JsonProperty("role_name")
@NotBlank(message = "角色名称不能为空")
private String roleName; @ApiModelProperty(value = "角色状态(0正常 1停用)", example = "0")
@Min(value = 0, message = "角色状态(0正常 1停用)")
@Max(value = 1, message = "角色状态(0正常 1停用)")
private byte status; @ApiModelProperty(value = "备注", example = "货主角色")
private String remark; @ApiModelProperty(value = "菜单id列表", example = "[1,2,3,4]")
private List<Long> menuIds;
}
效果演示
SpringBoot AOP完美记录用户操作日志,附源码的更多相关文章
- 我使用Spring AOP实现了用户操作日志功能
我使用Spring AOP实现了用户操作日志功能 今天答辩完了,复盘了一下系统,发现还是有一些东西值得拿出来和大家分享一下. 需求分析 系统需要对用户的操作进行记录,方便未来溯源 首先想到的就是在每个 ...
- RabbitMQ实战场景(一):异步记录用户操作日志
传统的项目开发中业务流程以串行方式,执行了模块1—>模块2–>模块3 而我们知道,这个执行流程其实对于整个程序来讲是有一定的弊端的,主要有几点: (1)整个流程的执行响应等待时间比较长; ...
- ssm 项目记录用户操作日志和异常日志
借助网上参考的内容,写出自己记录操作日志的心得!! 我用的是ssm项目使用aop记录日志:这里用到了aop的切点 和 自定义注解方式: 1.建好数据表: 数据库记录的字段有: 日志id .操作人.操作 ...
- springAOP记录用户操作日志
项目已经开发完成,需要加用户操作日志,如果返回去加也不太现实,所以使用springAOP来完成比较合适. 注解工具类: @Retention(RetentionPolicy.RUNTIME) @Tar ...
- 2.NetDh框架之简单高效的日志操作类(附源码和示例代码)
前言 NetDh框架适用于C/S.B/S的服务端框架,可用于项目开发和学习.目前包含以下四个模块 1.数据库操作层封装Dapper,支持多种数据库类型.多库实例,简单强大: 此部分具体说明可参考博客: ...
- Spring AOP使用注解记录用户操作日志
最后一个方法:核心的日志记录方法 package com.migu.cm.aspect; import com.alibaba.fastjson.JSON; import com.migu.cm.do ...
- springBoot 过滤器去除请求参数前后空格(附源码)
背景 : 用户在前端页面中不小心输入的前后空格,为了防止因为前后空格原因引起业务异常,所以我们需要去除参数的前后空格! 如果我们手动去除参数前后空格,我们可以这样做 @GetMapping(value ...
- 使用jsonp跨域调用百度js实现搜索框智能提示,并实现鼠标和键盘对弹出框里候选词的操作【附源码】
项目中常常用到搜索,特别是导航类的网站.自己做关键字搜索不太现实,直接调用百度的是最好的选择.使用jquery.ajax的jsonp方法可以异域调用到百度的js并拿到返回值,当然$.getScript ...
- linux 记录用户操作日志
将以下加入到/etc/profile 最后 history USER_IP=`who -u am i 2>/dev/null| awk '{print $NF}'|sed -e 's/[()]/ ...
- SAP中查询用户操作日志的事务码
事务码:STAD 注意:查询的时间跨度范围不要太大,否则会很慢! 事务码:ST03N 工作负载和性能统计
随机推荐
- flask3之CBV和session
flask的CBV CBV书写案例 from flask import Flask app=Flask(__name__) #FBA @app.route("/") def ind ...
- js RGB转HSV
function rgb2hsv (r,g,b) { var computedH = 0; var computedS = 0; var computedV = 0; //remove spaces ...
- 如何在多个 Git 平台玩转一个仓库
版本控制在软件开发中至关重要,而 Git 是广泛使用的代码管理工具.有时,我们可能需要在多个平台 (如 GitHub.GitLab 和 Gitee) 上同步同一 Git 仓库,以便备份.协作等. 本文 ...
- XML Schema 字符串数据类型及约束详解
字符串数据类型用于包含字符字符串的值.字符串数据类型可以包含字符.换行符.回车符和制表符. 以下是模式中字符串声明的示例: <xs:element name="customer&quo ...
- 【进阶篇】使用 Stream 流对比两个集合的常用操作分享
目录 前言 一.集合的比较 1.1需要得到一个新的流 1.2只需要一个简单 boolean 结果 二.简单集合的对比 2.1整型元素集合 2.2字符串元素集合 2.3其它比较 三.Stream 基础回 ...
- go append的坑
b := []int{1,2,3,4,5} slice := b[:2] newSlice := append(slice, 50) fmt.Println(b) fmt.Println(newSli ...
- linux server Vue 或其它单页面项目站点 nginx 实施部署
# nginx vue 处理前台路由 history 模式刷新 404 的问题 location / { try_files $uri $uri/ /index.html; if ($uri ~* . ...
- 谁说爬虫只能Python?看我用C#快速简单实现爬虫开发和演示!
前言:说到爬虫,基本上清一色的都知道用Python,但是对于一些没玩过或者不想玩Python的来说,却比较头大一点.所以以下我站在C# 的角度,来写一个简单的Demo,用来演示C# 实现的简单小爬虫. ...
- ChatTTS,语气韵律媲美真人的开源TTS模型,文字转语音界的新魁首,对标微软Azure-tts
前两天 2noise 团队开源了ChatTTS项目,并且释出了相关的音色模型权重,效果确实非常惊艳,让人一听难忘,即使摆在微软的商业级项目Azure-tts面前,也是毫不逊色的. ChatTTS是专门 ...
- SpringBoot系列(七) jpa的使用,以增删改查为例
JPA是Java Persistence API的简称,Java持久层API,是JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中. 它是SUN公司推出的一套基 ...