mysql分表分页

之前涛哥建议用代码拼接的方式去分表分页,而不是sql拼接(不要union,也不要list分页),我就这么做了,贴一下代码分享下,有点乱,有更好的建议欢迎提出:
详细代码参考(axxBank)com.aixuexi.axxBank.dao.IncomeRecordDetailDao#getActualAccountInfoByCondition

List<IncomeRecordDetailInfo> list = new ArrayList<IncomeRecordDetailInfo>();
DateFormat df = new SimpleDateFormat(DateConstant.FORMAT_PATTERN_YMD1);
List<String> dateList = FinaicialCommon.getDateArrayByDate(actualAccountParams.getStartDate(), actualAccountParams.getEndDate());//对接入的起始日期作处理,决定接下来要查哪些表
int startIndex = (pageBean.getPageNum() – 1) * pageBean. getPagesize();//分页的开始位置
int endIndex = pageBean.getPageNum() * pageBean.getPagesize();//分页的结束位置
int sum = 0,start,end;//sum(记录开始到当前的总结果数) start(limit的开始位置) end(limit的大小)
Map<String, Integer> mapCount = getIncomeCountList(actualAccountParams, dictionaryIds, dateList, df);//获取每个分表对应的结果数 以及总结果数 (key:dateString value:各分表的结果数)
for (int date = dateList.size() – 1; date >= 0; date –) {//默认按时间倒序
String dateString = dateList.get(date);
String tableIncomeString = new StringBuilder(“income_record_”).append(dateString).toString();//分表名
String tableAmountString = new StringBuilder(“amount_record_”).append(dateString).toString();//分表名
StringBuilder sb = 。。。。//对分表的sql查询,我就不贴代码了,可以到对应的代码处查看
if(isPage) {//是否需要分页()
int resultCount = mapCount.get(dateString) == null ? 0 : mapCount.get(dateString);//每次(每个分表)查询的结果数
/**以下是分表分页算法,用于拼接分表之后的分页查询结果**/
sum += resultCount;//记录当前的结果总数,用于分页拼接
if (sum >= startIndex) {//sum 当前的结果总数 startIndex 分页的开始位置
if (sum > endIndex) {//当前结果总数是否大于分页的结束位置(当前表已经满足分页请求了)
start = startIndex – (sum – resultCount) < 0 ? 0 : (startIndex – (sum – resultCount)); //计算查询当前表的开始位置 如果为负置为0
end = startIndex – (sum – resultCount) + pageBean.getPagesize();//查询当前表的结束位置
sb.append(” LIMIT “).append(start).append(“,”).append(end-start);//对当前表指定分页,分页大小直接pageSize也行
list.addAll(jdbcTemplate.query(sb.toString(), new IncomeRecordDetailMapper()));
break;
} else if (sum <= endIndex) {//当前结果总数小于 分页的结束位置(当前表不满足分页请求,拼接完当前表,则继续拼接下一个表)
start = (startIndex – (sum – resultCount)) < 0 ? 0 : (startIndex – (sum – resultCount));//当前表的开始位置 如果为负置0
end = resultCount; //当前表的结束位置,结束位置直接是当前表的总结果数
sb.append(” LIMIT “).append(start).append(“,”).append(end);//当前表指定分页
list.addAll(jdbcTemplate.query(sb.toString(), new IncomeRecordDetailMapper()));
}
}
}else {//不需要分页的话直接拼接所有结果
list.addAll(jdbcTemplate.query(sb.toString(),new IncomeRecordDetailMapper()));
}
}

微服务使用logback+打成tar包方式

hello,all

为推行微服务统一部署管理,需要将所有微服务项目打成tar包的形式。

同时,承祝强力推荐logback,因此也同时去掉了log4j,集成logback(性能好呀)。

下面简要介绍一下要点,注意摆正姿势:

参考项目:revolver,分支:assembly_new

1.打tar包

  • 在pom.xml里引入maven-assembly-plugin插件
<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>3.0.0</version>
    <configuration>
        <descriptors>
            <descriptor>src/main/assembly.xml</descriptor>
        </descriptors>
    </configuration>
    <executions>
        <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>
  • 在src/main目录里添加assembly.xml(assembly的配置文件),内容如下:
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
   <id>assembly</id>
   <formats>
      <format>tar.gz</format>
   </formats>
   <includeBaseDirectory>true</includeBaseDirectory>

   <fileSets>
      <fileSet>
         <directory>bin</directory>
         <outputDirectory>bin</outputDirectory>
         <fileMode>0755</fileMode>
         <filtered>false</filtered>
      </fileSet>
      <fileSet>
         <directory>src/main/resources</directory>
         <outputDirectory>conf</outputDirectory>
         <fileMode>0644</fileMode>
         <filtered>false</filtered>
      </fileSet>
      <fileSet>
         <directory>${resource.path}</directory>
         <outputDirectory>conf</outputDirectory>
         <fileMode>0644</fileMode>
         <filtered>false</filtered>
      </fileSet>
   </fileSets>
   <dependencySets>
      <dependencySet>
         <outputDirectory>lib</outputDirectory>
      </dependencySet>
   </dependencySets>
</assembly>
  • 在项目的根目录(与src平级)添加bin目录,里面放四个脚本:dump.shserver.shstart.shstop.sh。脚本太多,这里就不粘贴出来了,可以到参考项目里取。拷贝脚本到自己项目之后需要修改start.sh里的
START_CLASS=启动类全限定名

启动类如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.text.SimpleDateFormat;
import java.util.Date;

public class Main {
    private static Logger logger = LoggerFactory.getLogger(Main.class);

    private static final String CONFIG_FILE_SPRING = "spring.xml";
    private static volatile boolean running = true;
    private static ClassPathXmlApplicationContext context = null;

    public static void main(String[] args) {
        try {
            long before = System.currentTimeMillis();
            start();
            long time=System.currentTimeMillis()-before;
            String logLine = new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " dubbox server started! it takes "+time+" ms.";
            System.out.println(logLine);
            logger.info(logLine);
        } catch (RuntimeException e) {
            e.printStackTrace();
            logger.error(e.getMessage(), e);
            System.exit(1);
        }

        if(!"false".equals(System.getProperty("dubbo.shutdown.hook"))) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    context.stop();
                    Class t = Main.class;
                    synchronized(Main.class) {
                        running = false;
                        Main.class.notify();
                    }
                }
            });
        }

        synchronized (Main.class) {
            while (true) {
                try {
                    Main.class.wait();
                } catch (Throwable e) {
                }
            }
        }
    }

    private static void start() {
        context = new ClassPathXmlApplicationContext(new String[] { CONFIG_FILE_SPRING });
        context.start();
    }
}

注:脚本取自中间件组新项目里的bin目录下的脚本,然后加以修改。并且摒弃了原来deploy目录里的脚本。注意不要混合使用

  • 打了tar包之后,在线下jenkins部署的脚本如下
#!/bin/bash
echo "kill PID!!"
BUILD_ID=DONTKILLME
cd $WORKSPACE/target
echo "kill PID!"
ps -ef|grep $JOB_NAME|awk {'print $2'}|xargs kill -9
sleep 2
tar -zxvf $JOB_NAME-assembly.tar.gz
cd $JOB_NAME/bin
sh ./start.sh
sleep 2

2.引入logback

  • 在pom文件里引入logback依赖,1.2.3为logback目前最新版本。
<logback.version>1.2.3</logback.version>

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>${logback.version}</version>
</dependency>
  • 引入log4j-over-slf4j,并且在dubbo依赖里排除log4j。原因是dubbo默认使用lo4fj打印日志,而使用logback之后还想dubbo打印日志,需要如此配置。(SLF4J ship with a module called log4j-over-slf4j. It allows log4j users to migrate existing applications to SLF4J without changing a single line of code but simply by replacing the log4j.jar file with log4j-over-slf4j.jar, as described below.)
<slf4j.version>1.7.25</slf4j.version>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>${slf4j.version}</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.8.3</version>
    <exclusions>
        <exclusion>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>
  • 在resources_xxx里引入logback.xml,并且修改logDir的值为日志根目录,程序启动后会在日志根目录下创建四个文件夹debug、info、warn、error。不同级别的日志打到不同的文件夹里,并且按天滚动。
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!-- 定义日志文件 输出位置 -->
    <property name="logDir" value="/data/var/log/application/Revolver" />

    <property name="maxHistory" value="30" />

    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %p [USER_ID:%X{USER_ID}][%t][%L][%c.%M]%m%n</pattern>
        </encoder>
    </appender>

    <!-- ERROR级别日志 -->
    <appender name="errorFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logDir}/error/error.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logDir}/error/error.log.%d{yyyy-MM-dd}</fileNamePattern>
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %p [USER_ID:%X{USER_ID}][%t][%L][%c.%M]%m%n</pattern>
        </encoder>
    </appender>

    <!-- WARN级别日志 appender -->
    <appender name="warnFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logDir}/warn/warn.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARN</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logDir}/warn/warn.log.%d{yyyy-MM-dd}</fileNamePattern>
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %p [USER_ID:%X{USER_ID}][%t][%L][%c.%M]%m%n</pattern>
        </encoder>
    </appender>

    <!-- INFO级别日志 appender -->
    <appender name="infoFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logDir}/info/info.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logDir}/info/info.log.%d{yyyy-MM-dd}</fileNamePattern>
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %p [USER_ID:%X{USER_ID}][%t][%L][%c.%M]%m%n</pattern>
        </encoder>
    </appender>

    <!-- DEBUG级别日志 appender -->
    <appender name="debugFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logDir}/debug/debug.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logDir}/debug/debug.log.%d{yyyy-MM-dd}</fileNamePattern>
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %p [USER_ID:%X{USER_ID}][%t][%L][%c.%M]%m%n</pattern>
        </encoder>
    </appender>

    <root>
        <level value="info"/>
        <!-- 控制台输出 -->
        <appender-ref ref="console"/>
        <!-- 文件输出 -->
        <appender-ref ref="errorFile"/>
        <appender-ref ref="warnFile"/>
        <appender-ref ref="infoFile"/>
        <appender-ref ref="debugFile"/>
    </root>
</configuration>

以上

by the way:markdown写起来真的很舒服

基于大数据的用户画像

什么是用户画像?
简而言之,用户画像是根据用户社会属性、生活习惯和行为等信息而抽象出的一个标签化的用户模型。构建用户画像的核心工作即是给用户贴“标签”,而标签是通过对用户信息分析而来的高度精炼的特征标识。用户画像作为“大数据”的核心组成部分,在众多互联网公司中一直有其独特的地位。
举例来说,如果你经常购买一些玩偶玩具,那么电商网站即可根据玩具购买的情况替你打上标签“有孩子”,甚至还可以判断出你孩子大概的年龄,贴上“有5-10岁的孩子”这样更为具体的标签,而这些所有给你贴的标签统在一次,就成了你的用户画像,因此,也可以说用户画像就是判断一个人是什么样的人。

用户画像的作用
精准营销,分析产品潜在用户,针对特定群体利用短信邮件等方式进行营销;
用户统计,比如中国大学购买书籍人数 TOP10,全国分城市奶爸指数;
数据挖掘,构建智能推荐系统;
进行效果评估,完善产品运营,提升服务质量,其实这也就相当于市场调研、用户调研,迅速下定位服务群体,提供高水平的服务

数据收集
数据收集大致分为网络行为数据、服务内行为数据、用户内容偏好数据、用户交易数据这四类。

  • 网络行为数据:活跃人数、页面浏览量、访问时长、激活率、外部触点、社交数据等
  • 服务内行为数据:浏览路径、页面停留时间、访问深度、唯一页面浏览次数等
  • 用户内容便好数据:浏览/收藏内容、评论内容、互动内容、生活形态偏好、品牌偏好等
  • 用户交易数据(交易类服务):贡献率、客单价、连带率、回头率、流失率等

用户画像的系统架构:

2-9-880x450

美团架构

523

携程架构

上图是美团的和携程的系统架构,不知道是几年前的了。但是从两个架构里可以看出共同的部分:采集、计算、存储/查询和监控。采集的数据分为非实时和实时两种。数据的准确性是衡量用户画像价值的关键指标。

所以,我们现在要考虑的问题是:

我们为什么要给教师做画像?

我们怎么给教师做画像?

我们怎么评估我们给教师做的画像?

Java的Fork/Join任务,你写对了吗?

当我们需要执行大量的小任务时,有经验的Java开发人员都会采用线程池来高效执行这些小任务。然而,有一种任务,例如,对超过1000万个元素的数组进行排序,这种任务本身可以并发执行,但如何拆解成小任务需要在任务执行的过程中动态拆分。这样,大任务可以拆成小任务,小任务还可以继续拆成更小的任务,最后把任务的结果汇总合并,得到最终结果,这种模型就是Fork/Join模型。

Java7引入了Fork/Join框架,我们通过RecursiveTask这个类就可以方便地实现Fork/Join模式。

例如,对一个大数组进行并行求和的RecursiveTask,就可以这样编写:

class SumTask extends RecursiveTask<Long> {

    static final int THRESHOLD = 100;
    long[] array;
    int start;
    int end;

    SumTask(long[] array, int start, int end) {
    this.array = array;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        if (end - start <= THRESHOLD) {
            // 如果任务足够小,直接计算:
            long sum = 0;
            for (int i = start; i < end; i++) {
                sum += array[i];
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println(String.format("compute %d~%d = %d", start, end, sum));
            return sum;
        }
        // 任务太大,一分为二:
        int middle = (end + start) / 2;
        System.out.println(String.format("split %d~%d ==> %d~%d, %d~%d", start, end, start, middle, middle, end));
        SumTask subtask1 = new SumTask(this.array, start, middle);
        SumTask subtask2 = new SumTask(this.array, middle, end);
        invokeAll(subtask1, subtask2);
        Long subresult1 = subtask1.join();
        Long subresult2 = subtask2.join();
        Long result = subresult1 + subresult2;
        System.out.println("result = " + subresult1 + " + " + subresult2 + " ==> " + result);
        return result;
    }
}

编写这个Fork/Join任务的关键在于,在执行任务的compute()方法内部,先判断任务是不是足够小,如果足够小,就直接计算并返回结果(注意模拟了1秒延时),否则,把自身任务一拆为二,分别计算两个子任务,再返回两个子任务的结果之和。

最后写一个main()方法测试:

public static void main(String[] args) throws Exception {
    // 创建随机数组成的数组:
    long[] array = new long[400];
    fillRandom(array);
    // fork/join task:
    ForkJoinPool fjp = new ForkJoinPool(4); // 最大并发数4
    ForkJoinTask<Long> task = new SumTask(array, 0, array.length);
    long startTime = System.currentTimeMillis();
    Long result = fjp.invoke(task);
    long endTime = System.currentTimeMillis();
    System.out.println("Fork/join sum: " + result + " in " + (endTime - startTime) + " ms.");
}

关键代码是fjp.invoke(task)来提交一个Fork/Join任务并发执行,然后获得异步执行的结果。

我们设置任务的最小阀值是100,当提交一个400大小的任务时,在4核CPU上执行,会一分为二,再二分为四,每个最小子任务的执行时间是1秒,由于是并发4个子任务执行,整个任务最终执行时间大约为1秒。

新手在编写Fork/Join任务时,往往用搜索引擎搜到一个例子,然后就照着例子写出了下面的代码:

protected Long compute() {
    if (任务足够小?) {
        return computeDirect();
    }
    // 任务太大,一分为二:
    SumTask subtask1 = new SumTask(...);
    SumTask subtask2 = new SumTask(...);
    // 分别对子任务调用fork():
    subtask1.fork();
    subtask2.fork();
    // 合并结果:
    Long subresult1 = subtask1.join();
    Long subresult2 = subtask2.join();
    return subresult1 + subresult2;
}

很遗憾,这种写法是错!误!的!这样写没有正确理解Fork/Join模型的任务执行逻辑。

JDK用来执行Fork/Join任务的工作线程池大小等于CPU核心数。在一个4核CPU上,最多可以同时执行4个子任务。对400个元素的数组求和,执行时间应该为1秒。但是,换成上面的代码,执行时间却是两秒。

这是因为执行compute()方法的线程本身也是一个Worker线程,当对两个子任务调用fork()时,这个Worker线程就会把任务分配给另外两个Worker,但是它自己却停下来等待不干活了!这样就白白浪费了Fork/Join线程池中的一个Worker线程,导致了4个子任务至少需要7个线程才能并发执行。

打个比方,假设一个酒店有400个房间,一共有4名清洁工,每个工人每天可以打扫100个房间,这样,4个工人满负荷工作时,400个房间全部打扫完正好需要1天。

Fork/Join的工作模式就像这样:首先,工人甲被分配了400个房间的任务,他一看任务太多了自己一个人不行,所以先把400个房间拆成两个200,然后叫来乙,把其中一个200分给乙。

紧接着,甲和乙再发现200也是个大任务,于是甲继续把200分成两个100,并把其中一个100分给丙,类似的,乙会把其中一个100分给丁,这样,最终4个人每人分到100个房间,并发执行正好是1天。

如果换一种写法:

// 分别对子任务调用fork():
subtask1.fork();
subtask2.fork();

这个任务就分!错!了!

比如甲把400分成两个200后,这种写法相当于甲把一个200分给乙,把另一个200分给丙,然后,甲成了监工,不干活,等乙和丙干完了他直接汇报工作。乙和丙在把200分拆成两个100的过程中,他俩又成了监工,这样,本来只需要4个工人的活,现在需要7个工人才能1天内完成,其中有3个是不干活的。

其实,我们查看JDK的invokeAll()方法的源码就可以发现,invokeAll的N个任务中,其中N-1个任务会使用fork()交给其它线程执行,但是,它还会留一个任务自己执行,这样,就充分利用了线程池,保证没有空闲的不干活的线程。

关于服务分层的意见收集

我先起个头,说一下我的想法。
服务分为4层,并将部分中间件服务划为第一层,从底层向上层如下:
第一层*中间件服务:可以被任何服务调用的服务,为了和以下两种服务区分,可称其为中间件组件
第二层*底层独立微服务:不需要调用别的微服务的服务(但可调用中间件服务),例如财务微服务、基础数据微服务
第三层*业务微服务:从业务角度划分出来的微服务,可调用上面两种服务,但不可调用业务微服务
第四层*组合业务微服务:为抽象重复聚合业务微服务而形成的第二级的业务微服务,是对于业务微服务的包装,可调用上面三种服务(同样不可调用组合业务微服务)

Spring MVC 更灵活的控制 json 返回(自定义过滤字段)

前言:

其实这篇文章也不算完全的原创,因为是看了https://my.oschina.net/diamondfsd/blog/836727实践后做的总结,并且文章后面附上源码下载链接。

我们要做什么?

我们要在springmvc的控制层,在返回字符串的时候,动态改变一下到底要返回哪些字符串,比如我们要返回实体Article,他里面有4个属性我们只想返回其中的3个如图

如何实现?很简单加一个注解,如图,传入实体类型和拦截的属性名

当然这个注解不是springMVC自带的,是我们自定义的,下面就告诉大家如何实现。

1  首先我们要配置一个拦截器,拦截控制层的请求如图

2  实现HandlerMethodReturnValueHandler接口,其实就实现两个方法,一个是拦截后判定是否有对应的注解,也就是@JSON注解,如果有,就执行第二个方法,解析注解中的数据进行一些操作如图

3 我们定义一个类专门进行注解的解析,设置过滤器,和返回json 被jsonReturnHandler调用

4 过滤器

调用关系

源码(只包含工具类):https://pan.baidu.com/s/1jHCnySI

整个程序:https://pan.baidu.com/s/1dFgPNe5

总结:其实挺简单的,就是通过自定义注解,和拦截器实现注解的识别读取,通过jackson实现转换json,通过拦截器,拦截不需要的属性

————————————————chenchen

微信扫描公众号二维码,用户的网站账号自动登陆网站

 

微信公众号是在2012年8月份正式上线的。很荣幸,我在2013年底开始接触到了它并自己学习摸索开发了一些简单的功能,比如自动回复、自定义菜单。自动回复的时候如果是英文单词我还会帮用户翻译,类似于一个英语词典。最开始就是照着简洁的官方文档来做,慢慢摸索。不过后来接触多了慢慢发现,公众号的架构与开发者逻辑是很严谨也很安全的。举个简单的例子,比如要开发自定义菜单功能,需要用秘钥A和秘钥B去拿秘钥C,在后台管理里配置安全域名后,才可以用秘钥C用特定的方式去访问自定义菜单的接口。微信还有一个开放平台,我也使用过,那个就更复杂一点。

 

切入正题,后来换了一家公司,他们正好也会做一些公众号的业务,我就帮他们的网站做了一个个性的登录功能—-公众号扫码登录。这个登录不是“授权登录”,而是先把公众号与网站上自己的账号做绑定,然后网站的登录处选择公众号扫码登录,用户如果未关注则需要先关注公众号,然后绑定账号,如果已经关注且绑定了,扫码后公众号里则回复登录提示,点击后网站直接登录已经绑定的账号。关键点就在这个绑定的网站账号上,如果是授权登录,公众号就可以直接获取你的用户信息并登录,而扫码登录需要知道你在哪台电脑登录、让谁登录、何时登录。

下面是简易的技术流程图:

1

*角色:网站(web)、公众号项目(mobile)、用户(user)
登陆步骤:

1.网站在约定的场景id随机数里生成带参数二维码,供用户扫码(生成二维码以后网站不断轮询)
2.用户未关注时,让他关注公众号,并绑定自己的网站账号;已关注时则判断是否绑定账号,如果未绑定,跳转到绑定页面

3.已关注并绑定时直接回复消息“点击登陆网站”,用户点击登陆按钮,修改状态state=1(默认是0)
4.当网站查询到用户的登陆状态变化后(state=1),立刻返回给网站前端账号信息(同时恢复状态state=0),未查询到则返回-1(账号信息RC4加密)
5.当网站收到接口返回的账号信息后立刻将该账号登陆

synchonized加锁的虚拟机实现

synchonized实现有两种方式,一种是直接加在方法上,对整个方法进行加锁。另一种是对方法内的某一个代码块进行加锁,那么这两种加锁方式在虚拟机内的实现是怎样的呢。

对方法直接进行加锁:方法级的同步时隐式的,即无须通过字节码指令来控制,它实现在方法调用和返回操作之间。虚拟机可以从方法常量池的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法是否声明为同步方法。当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程就要先成功持有管程,然后才能执行方法,最后当方法完成时释放管程(无论是否正常完成都会释放)。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那么这个方法所持有的管程将在异常抛到同步方法之外时自动释放。

对方法内部的某一个代码块进行加锁:java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义,正确实现synchronized关键字需要javac 编译器和java虚拟机两者共同协作支持。在代码块执行开始的时候,将会调用monitorenter指令,当代码块结束时将调用monitorexit指令,编译器必须确保无论方法通过何种方式完成,方法中调用过的每条monitorenter指令都必须执行其对应的monitorexit指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时monitorenter和monitorexit指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有异常,它的目的就是用来执行monitorexit指令。

java 实现彩色图片转成黑白图片

因业务需求,前段时间做了彩色图片转为黑白图片。以下代码作为参考:

public static void changeImge(File img) {
try {
Image image = ImageIO.read(img);
int srcH = image.getHeight(null);
int srcW = image.getWidth(null);
BufferedImage bufferedImage = new BufferedImage(srcW, srcH,BufferedImage.TYPE_3BYTE_BGR);
bufferedImage.getGraphics().drawImage(image, 0,0, srcW, srcH, null);
bufferedImage=new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY),null).filter (bufferedImage,null);
FileOutputStream fos = new FileOutputStream(img);
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(fos);
encoder.encode(bufferedImage);
fos.close();
} catch (IOException e) {
e.printStackTrace();
throw new IllegalStateException("图片转换出错!", e);
}
}

 

策略模式简化代码的臃肿

策略模式的定义和使用场景

定义:策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们可以相互替换,让算法独立于使用它的客户而独立变化。

分析下定义,策略模式定义和封装了一系列的算法,它们是可以相互替换的,也就是说它们具有共性,而它们的共性就体现在策略接口的行为上,另外为了达到最后一句话的目的,也就是说让算法独立于使用它的客户而独立变化,我们需要让客户端依赖于策略接口。

策略模式的使用场景:

1.针对同一类型问题的多种处理方式,仅仅是具体行为有差别时;
2.需要安全地封装多种同一类型的操作时;
3.出现同一抽象类有多个子类,而又需要使用 if-else 或者 switch-case 来选择具体子类时。

抽象策略类

public interface Strategy {
    /**
     * 策略方法
     */
    public void strategyInterface();
}

具体策略类

public class ConcreteStrategyA implements Strategy {

    @Override
    public void strategyInterface() {
        //相关的业务
    }

}
public class ConcreteStrategyB implements Strategy {

    @Override
    public void strategyInterface() {
        //相关的业务
    }

}

环境角色类

public class Context {
    //持有一个具体策略的对象
    private Strategy strategy;
    /**
     * 构造函数,传入一个具体策略对象
     * @param strategy    具体策略对象
     */
    public Context(Strategy strategy){
        this.strategy = strategy;
    }
    /**
     * 策略方法
     */
    public void contextInterface(){

        strategy.strategyInterface();
    }

}

策略模式例子

假设鹅厂推出了3种会员,分别为会员,超级会员以及金牌会员,还有就是普通玩家,针对不同类别的玩家,购买《王者农药》皮肤有不同的打折方式,并且一个顾客每消费10000就增加一个级别,那么我们就可以使用策略模式,因为策略模式描述的就是算法的不同,这里我们举例就采用最简单的,以上四种玩家分别采用原价(普通玩家),九折,八折和七价的收钱方式。

那么我们首先要有一个计算价格的策略接口

public interface CalPrice {
    //根据原价返回一个最终的价格
    Double calPrice(Double orgnicPrice);
}

下面是4种玩家的计算方式的实现

public class Orgnic implements CalPrice {

    @Override
    public Double calPrice(Double orgnicPrice) {
        return orgnicPrice;
    }
}
public class Vip implements CalPrice {
    @Override
    public Double calPrice(Double orgnicPrice) {
        return orgnicPrice * 0.9;
    }
}
public class SuperVip implements CalPrice {
    @Override
    public Double calPrice(Double orgnicPrice) {
        return orgnicPrice * 0.8;
    }
}
public class GoldVip implements CalPrice {
    @Override
    public Double calPrice(Double orgnicPrice) {
        return orgnicPrice * 0.7;
    }
}

我们看客户类,我们需要客户类帮我们完成玩家升级的功能。

public class Player {
    private Double totalAmount = 0D;//客户在鹅厂消费的总额
    private Double amount = 0D;//客户单次消费金额
    private CalPrice calPrice = new Orgnic();//每个客户都有一个计算价格的策略,初始都是普通计算,即原价

    //客户购买皮肤,就会增加它的总额
    public void buy(Double amount) {
        this.amount = amount;
        totalAmount += amount;
        if (totalAmount > 30000) {//30000则改为金牌会员计算方式
            calPrice = new GoldVip();
        } else if (totalAmount > 20000) {//类似
            calPrice = new SuperVip();
        } else if (totalAmount > 10000) {//类似
            calPrice = new Vip();
        }
    }

    //计算客户最终要付的钱
    public Double calLastAmount() {
        return calPrice.calPrice(amount);
    }
}

在上面的基础上,我们可以使用简单工厂来稍微进行优化

public class CalPriceFactory {
    private CalPriceFactory(){}
    //根据客户的总金额产生相应的策略
    public static CalPrice createCalPrice(Player customer){
        if (customer.getTotalAmount() > 30000) {
            return new GoldVip();
        }else if (customer.getTotalAmount() > 20000) {
            return new SuperVip();
        }else if (customer.getTotalAmount() > 10000) {
            return new Vip();
        }else {
            return new Orgnic();
        }
    }
}

这样就将制定策略的功能从客户类分离了出来,我们的客户类可以变成这样。

public class Player {
    private Double totalAmount = 0D;//客户在鹅厂消费的总额
    private Double amount = 0D;//客户单次消费金额
    private CalPrice calPrice = new Orgnic();//每个客户都有一个计算价格的策略,初始都是普通计算,即原价

    //客户购买皮肤,就会增加它的总额
    public void buy(Double amount) {
        this.amount = amount;
        totalAmount += amount;
        /* 变化点,我们将策略的制定转移给了策略工厂,将这部分责任分离出去 */
        calPrice = CalPriceFactory.createCalPrice(this);
    }

    //计算客户最终要付的钱
    public Double calLastAmount() {
        return calPrice.calPrice(amount);
    }

    public Double getTotalAmount() {
        return totalAmount;
    }
}

虽然结合简单工厂模式,我们的策略模式灵活了一些,但不免发现在工厂中多了if-else判断,也就是如果增加一个会员类别,我又得增加一个else-if语句,这是简单工厂的缺点,对修改开放。

那有什么方法,可以较好的解决这个问题呢?那就是使用注解, 所以我们需要给注解加入属性上限和下限,用来表示策略生效的区间,用来解决总金额判断的问题。

1.首先我们做一个注解,这个注解是用来给策略添加的,当中可以设置它的上下限

//这是有效价格区间注解,可以给策略添加有效区间的设置
@Target(ElementType.TYPE)//表示只能给类添加该注解
@Retention(RetentionPolicy.RUNTIME)//这个必须要将注解保留在运行时
public @interface PriceRegion {
    int max() default Integer.MAX_VALUE;
    int min() default Integer.MIN_VALUE;
}

可以看到,我们只是使用这个注解来声明每一个策略的生效区间,于是对策略进行修改

@PriceRegion(max = 10000)
public class Orgnic implements CalPrice {

    @Override
    public Double calPrice(Double orgnicPrice) {
        return orgnicPrice;
    }
}
@PriceRegion(max=20000)
public class Vip implements CalPrice {
    @Override
    public Double calPrice(Double orgnicPrice) {
        return orgnicPrice * 0.9;
    }
}
@PriceRegion(min=20000,max=30000)
public class SuperVip implements CalPrice {
    @Override
    public Double calPrice(Double orgnicPrice) {
        return orgnicPrice * 0.8;
    }
}
@PriceRegion(min=3000)
public class GoldVip implements CalPrice {
    @Override
    public Double calPrice(Double orgnicPrice) {
        return orgnicPrice * 0.7;
    }
}

接下来就是在策略工厂中去处理注解

public class CalPriceFactory {
    private static final String CAL_PRICE_PACKAGE = "com.example.stragedemo";//这里是一个常量,表示我们扫描策略的包

    private ClassLoader classLoader = getClass().getClassLoader();

    private List<Class<? extends CalPrice>> calPriceList;//策略列表

    //根据玩家的总金额产生相应的策略
    public CalPrice createCalPrice(Player player) {
        //在策略列表查找策略
        for (Class<? extends CalPrice> clazz : calPriceList) {
            PriceRegion validRegion = handleAnnotation(clazz);//获取该策略的注解
            //判断金额是否在注解的区间
            if (player.getTotalAmount() > validRegion.min() && player.getTotalAmount() < validRegion.max()) {
                try {
                    //是的话我们返回一个当前策略的实例
                    return clazz.newInstance();
                } catch (Exception e) {
                    throw new RuntimeException("策略获得失败");
                }
            }
        }
        throw new RuntimeException("策略获得失败");
    }

    //处理注解,我们传入一个策略类,返回它的注解
    private PriceRegion handleAnnotation(Class<? extends CalPrice> clazz) {
        Annotation[] annotations = clazz.getDeclaredAnnotations();
        if (annotations == null || annotations.length == 0) {
            return null;
        }
        for (int i = 0; i < annotations.length; i++) {
            if (annotations[i] instanceof PriceRegion) {
                return (PriceRegion) annotations[i];
            }
        }
        return null;
    }

    //单例
    private CalPriceFactory() {
        init();
    }

    //在工厂初始化时要初始化策略列表
    private void init() {
        calPriceList = new ArrayList<Class<? extends CalPrice>>();
        File[] resources = getResources();//获取到包下所有的class文件
        Class<CalPrice> calPriceClazz = null;
        try {
            calPriceClazz = (Class<CalPrice>) classLoader.loadClass(CalPrice.class.getName());//使用相同的加载器加载策略接口
        } catch (ClassNotFoundException e1) {
            throw new RuntimeException("未找到策略接口");
        }
        for (int i = 0; i < resources.length; i++) {
            try {
                //载入包下的类
                Class<?> clazz = classLoader.loadClass(CAL_PRICE_PACKAGE + "." + resources[i].getName().replace(".class", ""));
                //判断是否是CalPrice的实现类并且不是CalPrice它本身,满足的话加入到策略列表
                if (CalPrice.class.isAssignableFrom(clazz) && clazz != calPriceClazz) {
                    calPriceList.add((Class<? extends CalPrice>) clazz);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    //获取扫描的包下面所有的class文件
    private File[] getResources() {
        try {
            File file = new File(classLoader.getResource(CAL_PRICE_PACKAGE.replace(".", "/")).toURI());
            return file.listFiles(new FileFilter() {
                public boolean accept(File pathname) {
                    if (pathname.getName().endsWith(".class")) {//我们只扫描class文件
                        return true;
                    }
                    return false;
                }
            });
        } catch (URISyntaxException e) {
            throw new RuntimeException("未找到策略资源");
        }
    }

    public static CalPriceFactory getInstance() {
        return CalPriceFactoryInstance.instance;
    }

    private static class CalPriceFactoryInstance {

        private static CalPriceFactory instance = new CalPriceFactory();
    }
}

虽然工厂里的逻辑增加了,但是解耦的效果达到了,现在我们随便加入一个策略,并设置好它的生效区间,策略工厂就可以帮我们自动找到适应的策略。

写在最后的话:

 

目前打算学生端的代码的重构,但是看到一些代码的维护性很差。一些业务逻辑和算法已经混合到一起了,导致修改业务逻辑的时候基本上是无从下手。如果着手代码的修改还不如重新垫付重做。从项目的一开始就梳理请代码的复杂度,或者多一点面向对象的开发习惯并利用些设计模式,以后的项目的可维护性可能会更好。我建议写代码要利用第三方校验代码格式的过程,checkStyle和findBug是很不错的检验工具。