spring商城项目学习笔记

乐观锁实现

核心思路就是每次要改变数据时先请求一个version号,如果改变的时候其他人改变成功了version会加,这时会无法改变该条数据,思路是用@version注解,也可以参考下面链接自己写sql语句
乐观锁实现参考
StackOverflow回答
存在的问题:由于用了mybatis自动生成代码,在加入自定义乐观锁实现后,每次更新数据表重新运行mybatis时会覆盖掉自添加的代码,mapper层和xml的代码都会覆盖,除非这个表不会再变直接从自动生成的配置文件里去除该表
另外一种思路是做行锁,加上for update的操作:
这里值得注意的是:InnoDB 默认是行级别的锁 , 当有明确指定的主键时候 , 是行级锁 , 否则是表级别,具体看一下以下的链接
ForUpdate示例

START TRANSACTION ; 
SELECT * FROM table_name WHERE id=1 FOR UPDATE ;
UPDATE table_name set count = count-1 WHERE id=1;
<!--
  乐观锁自定义实现
  -->
  <update id="updateCreditsCAS" parameterType="org.linlinjava.litemall.db.domain.LitemallUserCredits">
    <!--
      WARNING - @mbg.generated
      This element is automatically generated by MyBatis Generator, do not modify.
    -->
    update litemall_user_credits
    set user_id = #{userId,jdbcType=INTEGER},
    type_id = #{typeId,jdbcType=INTEGER},
    credit_name = #{creditName,jdbcType=VARCHAR},
    credits_count = #{creditsCount,jdbcType=INTEGER},
    expired_time = #{expiredTime,jdbcType=TIMESTAMP},
    create_time = #{createTime,jdbcType=TIMESTAMP},
    update_time = #{updateTime,jdbcType=TIMESTAMP},
    version = version+1
    where id = #{id,jdbcType=INTEGER} and version = #{version, jdbcType=INTEGER}
  </update>

获取请求body中的参数

1、 用@RequestBody JSONObject jsonObject

 public Object increaseStoreCredits(@RequestBody JSONObject jsonObject) {
        double amount = jsonObject.getDouble("amount");
        long orderId = jsonObject.getLong("orderId");
        }

2、写个JacksonUtil类来转换

mport com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.util.List;
import java.util.Map;

public class JacksonUtil {

    private static final Log logger = LogFactory.getLog(JacksonUtil.class);

    public static String parseString(String body, String field) {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode node;
        try {
            node = mapper.readTree(body);
            JsonNode leaf = node.get(field);
            if (leaf != null)
                return leaf.asText();
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }
      public static Integer parseInteger(String body, String field) {
        ObjectMapper mapper = new ObjectMapper();
        JsonNode node;
        try {
            node = mapper.readTree(body);
            JsonNode leaf = node.get(field);
            if (leaf != null)
                return leaf.asInt();
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
        return null;
    }

在Controller层调用

 public Object register(@RequestBody String body, HttpServletRequest request) {
        String username = JacksonUtil.parseString(body, "username");
        }

获取url路径参数

有两种路径参数:

  • /api/get?id=2
  • /api/get/id/2
    先说第一种/api/get?id=2
public Object querydeliveryFee(@RequestParam String id)
注意此时url里必须有id,跟代码里的id名要一样,如果你传了一个叫otherid,代码无法识别

再看第二种:/api/get/id/2

@RequestMapping(value="/api/{id}/{age}",method=RequestMethod.GET)
public String getName(ModelMap map,@PathVariable("id") String name,@PathVariable("age") int age)

SpringMVC中的路径参数和URL参数

自定义注解

这里记录一个@VisitorAccessible的注解,打上该注解的接口不需要登录,其他的需要登录。
VisitorAccessible 类:

@Inherited
@Retention(RUNTIME)
@Target({METHOD})
public @interface VisitorAccessible {
}

InitInterceptor类:实现该注解的拦截和操作

@Component
public class InitInterceptor implements HandlerInterceptor {

    private static final Log LOGGER = LogFactory.getLog(InitInterceptor.class);

    @Autowired
    private LitemallUserService userService;

    private NamedThreadLocal<Long> startTimeThreadLocal =
            new NamedThreadLocal<Long>("StopWatch-StartTime");


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type,Accept,X-Requested-With,token");

        long beginTime = System.currentTimeMillis();//1、开始时间
        startTimeThreadLocal.set(beginTime);
        if (request.getMethod().toUpperCase().equals("OPTIONS") || request.getServletPath().contains("ok") || request.getServletPath().contains("api/user/login")) {
            return true;
        }
        // 处理用户

        String token = request.getHeader("token");
        if (StringUtils.isNotEmpty(token)) {
            String userCode = "";
            try {
                userCode = (String) JWTVerifierUtil.verify(token).get(2);
                if (StringUtils.isEmpty(userCode)) {
                    throw new ApplicationException(CommonCodeEnum.NO_ACCESS_RIGHT);
                }
            } catch (Exception e) {
                throw new ApplicationException(CommonCodeEnum.NO_ACCESS_RIGHT);
            }

            LitemallUser userInfoEntity = userService.findById(Integer.valueOf(userCode));
            if (userInfoEntity == null) {
                throw new ApplicationException(CommonCodeEnum.NO_ACCESS_RIGHT);
            }

            UserVo userInfo = new UserVo();
            BeanUtils.copyProperties(userInfoEntity, userInfo);
            GlobalHolder.setCurrentLoginUser(userInfo);
        } else {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            VisitorAccessible annotation = handlerMethod.getMethodAnnotation(VisitorAccessible.class);
            if (annotation == null) {
                throw new ApplicationException(CommonCodeEnum.NO_ACCESS_RIGHT);
            }
        }
        return true;
    }

    /**
     * 在业务处理器处理请求执行完成后,生成视图之前执行的动作 可在modelAndView中加入数据,比如当前时间
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }

    /**
     * 在DispatcherServlet完全处理完请求后被调用,可用于清理资源等
     * 当有拦截器抛出异常时,会从当前拦截器往回执行所有的拦截器的afterCompletion() 处理写session工作
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        try {
            long endTime = System.currentTimeMillis();//2、结束时间
            long beginTime = startTimeThreadLocal.get();//得到线程绑定的局部变量(开始时间)
            long consumeTime = endTime - beginTime;//3、消耗的时间
            if (consumeTime > 500) {//此处认为处理时间超过500毫秒的请求为慢请求
                String ip = IpUtil.getIpAddr(request);
                LOGGER.info("ip:"+ ip +"request:"+ String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        GlobalHolder.removeCurrentLoginUser();

    }

}

GlobalHolder类:增删查当前用户

public class GlobalHolder {

    private static ThreadLocal<UserVo> currentLoginUser = new ThreadLocal<UserVo>();

    public static UserVo getCurrentLoginUser() {
        return currentLoginUser.get();
    }

    public static void setCurrentLoginUser(UserVo user) {
        currentLoginUser.set(user);
    }

    public static void removeCurrentLoginUser() {
        currentLoginUser.remove();
    }

}

WxWebMvcConfiguration :在配置类里配置需要拦截的访问路径为api开头

@Configuration
public class WxWebMvcConfiguration implements WebMvcConfigurer {
    @Autowired
    private InitInterceptor initInterceptor;

    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(initInterceptor).addPathPatterns("/api/**");
    }
}
@LoginUser获取用户token注解实现

这里核心是使用了HandlerMethodArgumentResolver的接口,先定义LoginUser的注解

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {

}
public class LoginUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
    public static final String LOGIN_TOKEN_KEY = "X-Litemall-Token";

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().isAssignableFrom(Integer.class) && parameter.hasParameterAnnotation(LoginUser.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container,
                                  NativeWebRequest request, WebDataBinderFactory factory) throws Exception {

//        return new Integer(1);
        String token = request.getHeader(LOGIN_TOKEN_KEY);
        if (token == null || token.isEmpty()) {
            return null;
        }

        return UserTokenManager.getUserId(token);
    }
}

HandlerMethodArgumentResolver实现教程
自定义注解拦截url

实现短信发送

手把手教学写短信

ClickSend短信发送平台

在pom文件引入

<dependency>
  <groupId>com.github.clicksend</groupId>
  <artifactId>clicksend-java-client</artifactId>
  <version>1.0.0</version>
  <scope>compile</scope>
</dependency>

发送短信代码:

public String sendSMS(String sendTo, String content) {
        ApiClient defaultClient = new ApiClient();
        defaultClient.setUsername("yourusername");
        defaultClient.setPassword("YourKey");
        SmsApi apiInstance = new SmsApi(defaultClient);

        SmsMessage smsMessage = new SmsMessage();
        smsMessage.body(content);
        smsMessage.to(sendTo);
        smsMessage.source("sdk");

        List<SmsMessage> smsMessageList = Arrays.asList(smsMessage);
        // SmsMessageCollection | SmsMessageCollection model
        SmsMessageCollection smsMessages = new SmsMessageCollection();
        smsMessages.messages(smsMessageList);
        try {
            String result = apiInstance.smsSendPost(smsMessages);
            System.out.println(result);
            return result;
        } catch (ApiException e) {
            System.err.println("Exception when calling SmsApi#smsSendPost");
            e.printStackTrace();
        }
        return "Something wrong";
    }

jar启动

代码里如果配置了日志目录,目录文件在当前执行java -jar的目录,2>&1代表把nohup的错误和info都输出在一个地方

nohup java -jar litemall-wx-api.jar >nohup.out 2>&1 &

数据库备份

#!/bin/bash

#备份路径
backup_path=/backup/mysql
#日期
current_date=`date "+%Y_%m_%d"`
#备份工具,如果找不到用whereis mysqldump
dump=/usr/local/bin/mysqldump
#连接数据库用户名
user=root
#连接数据库密码
password=yourpassword
#获取所有数据库
databases=$(/usr/local/bin/mysql -h192.168.213.135 -P31313 -uroot -pyourpassword -e "show databases" | grep -Ev "Database|sys|information_schema|performance_schema|mysql")
#循环备份
for db in $databases
do echo
echo ----------------$backup_path/${db}_$current_date.sql.gz BEGIN ----------------------
/usr/local/bin/mysqldump -h192.168.213.135 -P31313 -uroot -pyourpassword  --default-character-set=utf8 -q --lock-all-tables --flush-logs -E -R --triggers -B ${db} | gzip > $backup_path/${db}_$current_date.sql.gz
echo -------------------$backup_path/${db}_$current_date.sql.gz COMPLETE -----
echo
done
echo "done"


#删除三天前备份文件
echo "-----------delete 3days before files start -----------"
find /backup/mysql/ -type f -mtime +3 -exec rm {} \;

打包命令

mvn clean package -pl litemall-admin-api -am -Dmaven.test.skip=true

mvn clean package -pl litemall-wx-api -am -Dmaven.test.skip=true

git查看某个文件历史变化

最简单直观的方式是借助IDEA:在project源码中,找到目标文件–>右键Git–>show histroy,在每个commit里可以右击选择differ查看内容

  1. git log filename
    注意这里的filename属于文件的路径,比如:F:/workspace/shop-service/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/web/WxAuthController.java
    可以看到fileName相关的commit记录

  2. git log -p filename

可以显示每次提交的diff

  1. 只看某次提交中的某个文件变化,可以直接加上fileName

git show c5e69804bbd9725b5dece57f8cbece4a96b9f80b filename

省市区数据维护

澳洲行政区邮编数据,这里的数据还不够细化,一个悉尼的城区会对应多个邮编,需要细化到城区里的具体哪个区
在这里插入图片描述
官方统计数据

Mysql排除某个查询结果

使用场景:查询用户信息,但是信息表里包含了密码的敏感信息,需要忽略他,比较笨的方式是一个个select出来,但是如果字段很多就显得冗余。
有三种思路:
1、结合Mybatis plus插件来做,还没实验过
在这里插入图片描述
2、用@JsonIgnore注解:import com.fasterxml.jackson.annotation.JsonIgnore;
这里只是针对返回为JSON数据时才会忽略,如果只是单纯的返回对象,他还是会显示出查询的结果来

3、写一个返回类,不包含其你要忽略的字段

idea去除mapper.xml的黄色显示背景

去除教程

删除/**类的注释

删除摸我

删除 java 注释 /* */:/\*{1,2}[\s\S]*?\*/

删除 java 注释 //://[\s\S]*?\n

删除xml注释:<!-[\s\S]*?-->

删除空白行:^\s*\n

idea一直卡在Scanning files to index

打开项目后idea一直卡在这个页面,且卡死,此时只要找到前端代码的node_modules文件夹,右击将其exclude即可
在这里插入图片描述

MySQL设置了默认值但是插入后显示为null

看下网上的解答,查看一下代码果然是,不用selective的话mybatis会把没写的填充成null传过去,此时数据库又认为你传值过来了,null就狸猫换太子

你是不是用了持久化工具如mybatis这样的;
然后按照实体来进行插入?
1.mysql字段默认的含义:在插入时不指定该字段的值;
2.以mybatis举例,如果是插入实体,那么为空的字段就会插入空;
3.如果不想mybatis等持久化工具插入空,可以尝试insertSelective方式进行,这样为空字段会被剔除

idea设置jetbrain mono字体

发现了一个非常适合代码的字体,安装摸我

xtrabackup安装与使用

##install
wget https://www.percona.com/downloads/XtraBackup/Percona-XtraBackup-2.4.4/\
binary/redhat/7/x86_64/percona-xtrabackup-24-2.4.4-1.el7.x86_64.rpm

yum localinstall percona-xtrabackup-24-2.4.4-1.el7.x86_64.rpm
## uninstall
yum remove percona-xtrabackup

备份命令只能在数据库服务器上执行。
备份到远程服务器
在这里插入图片描述

专有云oss配置(一般不需要该配置)

修改配置文件
您需要运行以下命令,修改 /etc/sysctl.conf 配置文件。

sudo vi /etc/sysctl.conf

对比和添加参数
对照以下参数编辑配置文件,添加缺少的参数。参数的 key 和 value 都需要与以下内容保持一致

vm.max_map_count = 8388608
net.ipv4.tcp_rmem = 4096 87380 4194304
net.ipv4.tcp_wmem = 4096 16384 4194304
net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.netdev_max_backlog = 204800
net.core.somaxconn = 204800
net.ipv4.tcp_max_orphans = 3276800
net.ipv4.tcp_max_syn_backlog = 204800
net.ipv4.tcp_tw_recycle = 0
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.tcp_syncookies = 0

重新加载内存参数
修改完毕后,运行以下命令重新加载内存参数。

sudo /sbin/sysctl -p
专有云配置阿里OSS

nginx访问文件提示403

主要问题在于把静态文件放到了/root/www下,而此时的nginx启动是没有以root用户启动,要么把文件移动到非root目录,要么在nginx.conf里加入user root,再重新启动即可

nginx开启访问日志及错误日志

在nginx.conf主配置文件http模块里要设置日志格式,

log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

再到具体的配置文件里开启日志:

access_log          /service/access_log/admin_front.log  main;
        error_log          /service/access_log/admin_front_error.log  error;

nginx403禁止访问解决

遗留问题

1、Optional类针对空指针的使用
2、Goods分页查询的使用
3、Git解决冲突,防止代码丢失
4、mybatis自定义sql语句
5、mysql高可用方案:xtraback远程备份,目前只能本地
6、服务器安全防护:nginx,限制访问静态资源,跨域等安全问题
7、后台开放设置用户注销,禁用,启用状态
8、前端根据用户状态认证登录
9、数据库备份过慢,不知是否为网速问题
远程备份只能是从数据库本地备份再scp到远程存储服务器里。
远程备份xtrabackup
optional官方教程最好先看lambda
lambda part1官方教程
lambda part2

MHA搭建数据库集群
MHA另一教程
nginx安全配置

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值